By default the Kurtosis CDK package will deploy a timelock contract that can be used as an admin. For the sake of simplified testing, we don’t configure the timelock as the primary admin of the rollup manager. We want to show how you could use the timelock in order to get a better understanding.
Assuming that you’ve already full spun up your network, let’s get the details of our deployment.
kurtosis service exec cdk-v1 contracts-001 'cat /opt/zkevm/combined.json'
The command was successfully executed and returned '0'. Output was:
{
"polygonRollupManagerAddress": "0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2",
"polygonZkEVMBridgeAddress": "0xD71f8F956AD979Cc2988381B8A743a2fE280537D",
"polygonZkEVMGlobalExitRootAddress": "0x1f7ad7caA53e35b4f0D138dC5CBF91aC108a2674",
"polTokenAddress": "0xEdE9cf798E0fE25D35469493f43E88FeA4a5da0E",
"zkEVMDeployerContract": "0xe5CF69183CFCF0571E733D59a1a53d4E6ceD6E85",
"deployerAddress": "0xE34aaF64b29273B7D567FCFc40544c014EEe9970",
"timelockContractAddress": "0x07783C37CAAFe0f05C4105250C032062A83F7AC2",
"deploymentRollupManagerBlockNumber": 19,
"upgradeToULxLyBlockNumber": 19,
"admin": "0xE34aaF64b29273B7D567FCFc40544c014EEe9970",
"trustedAggregator": "0xCae5b68Ff783594bDe1b93cdE627c741722c4D4d",
"proxyAdminAddress": "0xB93b2fD69CE28f0DB91842aBFa40720d7e2B8fd7",
"salt": "0x0000000000000000000000000000000000000000000000000000000000000001",
"polygonDataCommitteeAddress": "0x5A6896A98c4B7C7E8f16d177C719a1d856b9154c",
"firstBatchData": {
"transactions": "0xf9010380808401c9c38094d71f8f956ad979cc2988381b8a743a2fe280537d80b8e4f811bff7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40d5f56745a118d0906a34e69aec8c0db1cb8fa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005ca1ab1e0000000000000000000000000000000000000000000000000000000005ca1ab1e1bff",
"globalExitRoot": "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5",
"timestamp": 1713829086,
"sequencer": "0x5b06837A43bdC3dD9F114558DAf4B26ed49842Ed"
},
"genesis": "0xd619a27d32e3050f2265a3f58dd74c8998572812da4874aa052f0886d0dfaf47",
"createRollupBlockNumber": 23,
"rollupAddress": "0x1Fe038B54aeBf558638CA51C91bC8cCa06609e91",
"verifierAddress": "0xf22E2B040B639180557745F47aB97dFA95B1e22a",
"consensusContract": "PolygonValidiumEtrog",
"polygonZkEVMGlobalExitRootL2Address": "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa"
}
In this case, it looks like my timelock is deployed at
0x07783C37CAAFe0f05C4105250C032062A83F7AC2
.
Let’s confirm that the admin
0xE34aaF64b29273B7D567FCFc40544c014EEe9970
is actually a default
admin for the rollup manager. First we need to check what the admin
role is:
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 'DEFAULT_ADMIN_ROLE()(bytes32)'
0x0000000000000000000000000000000000000000000000000000000000000000
Now let’s see if the admin account has this role:
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 'hasRole(bytes32,address)(bool)' 0x0000000000000000000000000000000000000000000000000000000000000000 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
true
Let’s also confirm that the time lock does not have the default admin role:
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 'hasRole(bytes32,address)(bool)' 0x0000000000000000000000000000000000000000000000000000000000000000 0x07783C37CAAFe0f05C4105250C032062A83F7AC2
false
Okay this looks good. Let’s first use the current admin account to grant admin access to the time lock:
cast send --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 \
--private-key 0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625 \
'grantRole(bytes32,address)' 0x0000000000000000000000000000000000000000000000000000000000000000 0x07783C37CAAFe0f05C4105250C032062A83F7AC2
blockHash 0x2a6ab08a2e87865a177bd24d16c96513c54cd08814f57aa73199712f5c71d0c0
blockNumber 12342
contractAddress
cumulativeGasUsed 58147
effectiveGasPrice 3000000007
from 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
gasUsed 58147
logs [{"address":"0x2f50ef6b8e8ee4e579b17619a92de3e2ffbd8ad2","topics":["0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d","0x0000000000000000000000000000000000000000000000000000000000000000","0x00000000000000000000000007783c37caafe0f05c4105250c032062a83f7ac2","0x000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee9970"],"data":"0x","blockHash":"0x2a6ab08a2e87865a177bd24d16c96513c54cd08814f57aa73199712f5c71d0c0","blockNumber":"0x3036","transactionHash":"0xef9151dadc11aed67dd567426a610b9ff04c88beaad1a0a540fb02fb74a1be5d","transactionIndex":"0x0","logIndex":"0x0","removed":false}]
logsBloom 0x00000004000000000000000000000000000000400000000000008000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000010000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000020000000400000000000000000000000000000000000000000001000000200000000
root
status 1
transactionHash 0xef9151dadc11aed67dd567426a610b9ff04c88beaad1a0a540fb02fb74a1be5d
transactionIndex 0
type 2
to 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2
Okay, it looks like that transaction worked, let’s confirm that the
timelock address is actually an admin now. This call previously
returned false
. Hopefully it returns true
now.
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 'hasRole(bytes32,address)(bool)' 0x0000000000000000000000000000000000000000000000000000000000000000 0x07783C37CAAFe0f05C4105250C032062A83F7AC2
true
Great, it looks like we’re headed in the right direction. Now let’s confirm the setup of our timelock. In particular, we should make sure that our timelock admin is setup correctly.
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 'DEFAULT_ADMIN_ROLE()(bytes32)'
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 'EXECUTOR_ROLE()(bytes32)'
0x0000000000000000000000000000000000000000000000000000000000000000
0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63
Now that we have the roles, let’s check if the admin account has them:
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 'hasRole(bytes32,address)(bool)' 0x0000000000000000000000000000000000000000000000000000000000000000 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 'hasRole(bytes32,address)(bool)' 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
false
true
It looks like our typical admin account is configured as an executor for the timelock, but not actually the admin. This means we should be able to execute transactions, but it doesn’t look like we would be able to change the delay. From the code it looks like the timelock is the only thing that can update its delay.
At this point, the question is can we use the timelock to make an admin call. Ideally we would revoke the admin’s account as the default admin role in the rollup manager. Let’s see if it would even work.
cast call --from 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 'revokeRole(bytes32,address)' 0x0000000000000000000000000000000000000000000000000000000000000000 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
0x
This is a good sign, it means the timelock has the ability to revoke admin access from the admin account in the rollup manager. Now we just need to schedule the call. First let’s get the call data.
cast calldata 'revokeRole(bytes32,address)' 0x0000000000000000000000000000000000000000000000000000000000000000 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
0xd547741f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee9970
This looks good, now we need to schedule the call
target="0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2"
value="0"
calldata="0xd547741f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee9970"
predecessor="0x0000000000000000000000000000000000000000000000000000000000000000"
salt="0x0000000000000000000000000000000000000000000000000000000000000000"
delay="3601"
cast send \
--private-key 0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625 \
--rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'schedule(address,uint256,bytes,bytes32,bytes32,uint256)' "$target" "$value" "$calldata" "$predecessor" "$salt" "$delay"
blockHash 0xacd613e435f89d04c07fdca6e58b2abef1d5d0f2473774950c6ff5335d24c055
blockNumber 12761
contractAddress
cumulativeGasUsed 67681
effectiveGasPrice 3000000007
from 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
gasUsed 67681
logs [{"address":"0x07783c37caafe0f05c4105250c032062a83f7ac2","topics":["0x4cf4410cc57040e44862ef0f45f3dd5a5e02db8eb8add648d4b0e236f1d07dca","0x2834e50d0fbd2359263689c685f4afd0311de4b150625c349a40a7b2b7e7f34e","0x0000000000000000000000000000000000000000000000000000000000000000"],"data":"0x0000000000000000000000002f50ef6b8e8ee4e579b17619a92de3e2ffbd8ad2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e110000000000000000000000000000000000000000000000000000000000000044d547741f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee997000000000000000000000000000000000000000000000000000000000","blockHash":"0xacd613e435f89d04c07fdca6e58b2abef1d5d0f2473774950c6ff5335d24c055","blockNumber":"0x31d9","transactionHash":"0x4fd7c8cd80b05a4472748aa6cdf8e82a5a857fc02f90c231c01816bcb0fd2b73","transactionIndex":"0x0","logIndex":"0x0","removed":false}]
logsBloom 0x00000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000004000200000000000000000000000000000000000000020000000000000000000800000000000000000000000000400000000000000000000000000000000000000000000040000000000000000000000000000000080000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000400000000000000020000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x4fd7c8cd80b05a4472748aa6cdf8e82a5a857fc02f90c231c01816bcb0fd2b73
transactionIndex 0
type 2
to 0x07783C37CAAFe0f05C4105250C032062A83F7AC2
Okay this looks successful, that should mean our call is scheduled. Let’s take a look at the logs:
Event | CallScheduled(bytes32,uint256,address,uint256,bytes,bytes32,uint256) | 0x4cf4410cc57040e44862ef0f45f3dd5a5e02db8eb8add648d4b0e236f1d07dca |
That means our id is 0x2834e50d0fbd2359263689c685f4afd0311de4b150625c349a40a7b2b7e7f34e
With that, we should be able to check on the status:
id="0x2834e50d0fbd2359263689c685f4afd0311de4b150625c349a40a7b2b7e7f34e"
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'isOperation(bytes32)(bool)' "$id"
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'isOperationPending(bytes32)(bool)' "$id"
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'isOperationReady(bytes32)(bool)' "$id"
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'isOperationDone(bytes32)(bool)' "$id"
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'getTimestamp(bytes32)(uint256)' "$id"
true
true
false
false
1713985543
This looks good, it looks like our operation is scheduled. We now just
need to wait until 1713985543
.
printf "%d\n" $(cast block -j --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) | jq -r '.timestamp')
1713983298
It looks like we still have to wait 37 minutes.
Once the time is elapsed, I should be able to repeat the same exact
call from schedule
and use execute
instead. First let’s make sure
that it reports that it’s ready.
id="0x2834e50d0fbd2359263689c685f4afd0311de4b150625c349a40a7b2b7e7f34e"
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'isOperationReady(bytes32)(bool)' "$id"
true
Nice, that looks good. Let’s execute it:
target="0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2"
value="0"
calldata="0xd547741f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee9970"
predecessor="0x0000000000000000000000000000000000000000000000000000000000000000"
salt="0x0000000000000000000000000000000000000000000000000000000000000000"
cast send \
--private-key 0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625 \
--rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x07783C37CAAFe0f05C4105250C032062A83F7AC2 \
'execute(address,uint256,bytes,bytes32,bytes32)' "$target" "$value" "$calldata" "$predecessor" "$salt"
blockHash 0x32fe6a5224885c605eeca7052cb89925f018965d4ac176b1e99ceff3ad4e9b1c
blockNumber 14076
contractAddress
cumulativeGasUsed 55161
effectiveGasPrice 3000000007
from 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
gasUsed 55161
logs [{"address":"0x2f50ef6b8e8ee4e579b17619a92de3e2ffbd8ad2","topics":["0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee9970","0x00000000000000000000000007783c37caafe0f05c4105250c032062a83f7ac2"],"data":"0x","blockHash":"0x32fe6a5224885c605eeca7052cb89925f018965d4ac176b1e99ceff3ad4e9b1c","blockNumber":"0x36fc","transactionHash":"0x7f0de30fe4db193ed75aba6f2c3862da8a1f8cef63e4408b8bbd802cc892559e","transactionIndex":"0x0","logIndex":"0x0","removed":false},{"address":"0x07783c37caafe0f05c4105250c032062a83f7ac2","topics":["0xc2617efa69bab66782fa219543714338489c4e9e178271560a91b82c3f612b58","0x2834e50d0fbd2359263689c685f4afd0311de4b150625c349a40a7b2b7e7f34e","0x0000000000000000000000000000000000000000000000000000000000000000"],"data":"0x0000000000000000000000002f50ef6b8e8ee4e579b17619a92de3e2ffbd8ad2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044d547741f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e34aaf64b29273b7d567fcfc40544c014eee997000000000000000000000000000000000000000000000000000000000","blockHash":"0x32fe6a5224885c605eeca7052cb89925f018965d4ac176b1e99ceff3ad4e9b1c","blockNumber":"0x36fc","transactionHash":"0x7f0de30fe4db193ed75aba6f2c3862da8a1f8cef63e4408b8bbd802cc892559e","transactionIndex":"0x0","logIndex":"0x1","removed":false}]
logsBloom 0x00000000000000000000000000000000000000400000000000008000000000000000000000000000000008000000000000200000000800000000000000000000000000000000000000000000000004000200000002000000000000000000002040000000020000000000000000000800000000000000000000000000400000000000000040000000000000000000000000000040000000000000010000000000000000090000000000000000000000000000000000000004000000000000000000000000000000080000000000000000000000000000000000000000000020000000400000000000000000000000000000000000000000001000000200000000
root
status 1
transactionHash 0x7f0de30fe4db193ed75aba6f2c3862da8a1f8cef63e4408b8bbd802cc892559e
transactionIndex 0
type 2
to 0x07783C37CAAFe0f05C4105250C032062A83F7AC2
Okay, I think that looks good. Let’s make sure that the role has
actually been revoked. This previously returned true
and we hope
that it’s false
now:
cast call --rpc-url $(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc) 0x2F50ef6b8e8Ee4E579B17619A92dE3E2ffbD8AD2 'hasRole(bytes32,address)(bool)' 0x0000000000000000000000000000000000000000000000000000000000000000 0xE34aaF64b29273B7D567FCFc40544c014EEe9970
false
Okay this looks good. Just to recap that has happened:
- We deployed the rollup manager using
test
mode which assigns the admin as the admin account rather than the timelock - We granted the
DEFAULT_ADMIN_ROLE
to the timelock addres - We scheduled a time locked transaction to revoke the admin account’s
DEFAULT_ADMIN_ROLE
- We executed the transaction after the elapsed amount of time.
- https://github.com/0xPolygonHermez/zkevm-contracts/blob/v6.0.0-rc.1-fork.9/contracts/v2/PolygonRollupManager.sol
- https://github.com/0xPolygonHermez/zkevm-contracts/blob/v6.0.0-rc.1-fork.9/contracts/PolygonZkEVMTimelock.sol
- https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/governance/TimelockController.sol