diff --git a/Makefile b/Makefile index b064b95..9a9c313 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PRIVATE_KEY?="0x3e17bc938ec10c865fc4e2d049902716dc0712b5b0e688b7183c16807234a84c" -RPC_URL?="https://0.0.0.0:8545" +RPC_URL?="http://0.0.0.0:8545" # Run ethereum nodes run-geth: diff --git a/config/LagrangeService.json b/config/LagrangeService.json index 63b71ae..127ffe8 100644 --- a/config/LagrangeService.json +++ b/config/LagrangeService.json @@ -9,7 +9,7 @@ "tokens": [ { "token_name": "mantle", - "token_address": "0xbB9dDB1020F82F93e45DA0e2CFbd27756DA36956", + "token_address": "0x1D7Acca2751281Bd27d8254fC2fCd71a5243626c", "multiplier": 1000000000 } ], diff --git a/config/accounts.json b/config/accounts.json index 7f21ac4..6956efa 100644 --- a/config/accounts.json +++ b/config/accounts.json @@ -7,16 +7,5 @@ "0xBD2369a9535751004617bC47cB0BF8Ea5c35Ed7C": "0x220ecb0a36b61b15a3af292c4520a528395edc51c8d41db30c74382a4af4328d", "0x83070c799c0d41526D4c71e462557CdbB2C750AC": "0x2a1b271106503777361139b2d28c3f360ce980e2dab0c18f4684d5b417ac46b3", "0x7365666466f97E8aBBEE8900925521e0469A1f25": "0x311fb4aa77facbc0debb6d9d88d3b4047115d905a4ff7b4399fc164494a75e3c", - "0xaa58F0fC9eddeFBef9E4C1c3e81f8cA5f22b9B8e": "0x897ae21c31176946115ad9174145c2e8b755e1be0c1b4987a63db790349e8e15", - "0x40e1201138f0519877e3704F253153C92f5cfD2a": "0x9750901b0ded0603e9be4a56315fe1487d4afc7ec05e3fc75fe6c568d52bea1b", - "0xF4F16235e0591BD0BD21772718288126388C52c7": "0xf1efa79837b4ea45d2345ff3946859dda9afe131b97bcb0654927248f4eb2918", - "0x3928b81f4f36913055B9D127F6F4f4EBc47B03bd": "0x5055f4029a4b83fd3e2c94e0c9baa9042529a067d42cec843c7d109c5b5756e0", - "0xb6e28D040F412054e769d8Fa01964fD30fe585a5": "0x913f118220494d05facd61f2a4919d2d10148042cffb2732c30c10a759713d7f", - "0x9fBDa3FC11494bF357FD2041631836795322Dd33": "0x5b8e5f0e79175b7098a1cea3972a6739b0bbbcee527cec13d6a0dd605858e389", - "0xf431E2fd356150677831Be391b2a73D37EDeeA60": "0xa617dc90522527902e6cc78c367b1a61dfc8e9f0b6cde537e281623f6da80a3d", - "0xD1b53572F9d2F186dE2B6319DAF70dD724888b79": "0x10c77ae8faf550e9c23b07856c2de36c992b8f0bb7497409c133b2caede81295", - "0xeEB0A6E0dD3C77113D7A1d876e6Fbb06f8e8F465": "0x4906854b045b4d8d51dfc696786391de44bd7996b7a66e268453dd0609af45bb", - "0xDAD2031D449F8C61A852f3d5b3c3f1e9425FA4f9": "0xfec75f4d982cce5e1e43d41d55557ff58875e7e44b0a3f4e06e97ea437d8162a", - "0x84634743616036a45Ce02D8d6A2B06F8e4ED37b7": "0x37652b3171834042ce595e8b16f9b6705ba678231030d11fcae3ecec254454b0", - "0x3cDE5DD353Bf3c8C0D3D7d905436D7A7c3C369a2": "0xa9f8fe458bddbb32c12e6bfbef75f313efff844565ba38b3098d6f88b88c7075" + "0xaa58F0fC9eddeFBef9E4C1c3e81f8cA5f22b9B8e": "0x897ae21c31176946115ad9174145c2e8b755e1be0c1b4987a63db790349e8e15" } \ No newline at end of file diff --git a/config/operators.json b/config/operators.json index c6b18d2..0542e47 100644 --- a/config/operators.json +++ b/config/operators.json @@ -13,42 +13,42 @@ "0xaa58F0fC9eddeFBef9E4C1c3e81f8cA5f22b9B8e" ], "bls_pub_keys": [ - "0x86b50179774296419b7e8375118823ddb06940d9a28ea045ab418c7ecbe6da84d416cb55406eec6393db97ac26e38bd4", - "0x90aa5215784d04f54d2df41cca557514c25b26cd0803da59c08eb3954633c4fe5131e195b1483123f5c9fe9f08200a6d", - "0xa771bcc2d948fa9801916e0c18aa27732f77d43b751138a3e119928103826427a5c11080620f9a9e37d8f4de3a4868cf", - "0xb5695acd75a5d52e82eddf4ae1c01a1d456085da4ce255169cdac877b5a622386e7db6cb9c4c39b4eaf660dfa3a80d5d", - "0x9496160c06e86aae28d251e082173b49ff10225f29611fae766d1a1a473d4bcf1188738208df163c01927b8c5df5160a", - "0x837ca7f100239253d16d982102e47387a369cd1ba4bf9cf08ab85bd70c2b7559e7158196d9c60d03a571dfb3580b2b8e", - "0x91f8a57e971b831b80f1f6c04eed120c2c3ecb9d13adf7601e05614909158c3882bdbc04251cb689ecf653c28b981774", - "0xb004ff3a1e4aa11af5d951d7aedc33ecca16a6bb7e639e81650659c6f896cddcaa82bd8e33c0b991776140731129b1e5", - "0x969d9fc322c302e418d6e73367bfb001d5a62008095126e10672f355918398fc3a70b0a96ec262693e9c6de301cd10fe" + "0xb80423265ab0f4beeb192c5d62a3cee0502adb86b87885a60da2113aa8ad82200a5a019fd5a06d0e032054193916e7f1", + "0xaa6882d7d2f190340fbe30582cca08e23f959c4c5c876e52d9e37ca5b560e143e23e7f45b8209fef4821b7624cd4b3cf", + "0x909416e9d5408a6917376bca0ce7192b5e03250a1776ecdaaea5e6a53a10c911631a06864a94e5e7d451c3c8e1c2c556", + "0xada0ff2c7aa7b25d9663cdae239b1bb72672641960208a7ceca4cae576db1b5dc54a1eb6ccbade07cb5a688879045caa", + "0xabbebad3349b4d200b79c238570bf65199b1e4b302f2b38f9a132b145bdc64a24f5e83df0e104152daf08248a5973566", + "0xb73537f19bd9217eb1ac9b068a7c1dad51d4802eaba374e920356373dc1dd80c0d066aa58b00859a4de30446ab8b87a2", + "0x8e90789947f959b853fb9c4aebeaa0348b86490db30a96ae7fc1049fde94afaeba7059b30e65b697751cd17f0a0f0e11", + "0x8853b3a124aa5a59f3179009f633dfc0b322ca77c6f423b027c45b20c7bd6bb0de1f15cdaed6f9ff56562e93b6b70c2c", + "0xafeeb4ce8cdc76adb250dd1d2314fb4da2adc4f35584867f72a2fca47a35548ebd1d95ba8f11134440419c7fa62a25eb" ], "chain_id": 1337 }, { "chain_name": "optimism", "operators": [ - "0x40e1201138f0519877e3704F253153C92f5cfD2a", - "0xF4F16235e0591BD0BD21772718288126388C52c7", - "0x3928b81f4f36913055B9D127F6F4f4EBc47B03bd", - "0xb6e28D040F412054e769d8Fa01964fD30fe585a5", - "0x9fBDa3FC11494bF357FD2041631836795322Dd33", - "0xf431E2fd356150677831Be391b2a73D37EDeeA60", - "0xD1b53572F9d2F186dE2B6319DAF70dD724888b79", - "0xeEB0A6E0dD3C77113D7A1d876e6Fbb06f8e8F465", - "0xDAD2031D449F8C61A852f3d5b3c3f1e9425FA4f9" + "0x6E654b122377EA7f592bf3FD5bcdE9e8c1B1cEb9", + "0x516D6C27C23CEd21BF7930E2a01F0BcA9A141a0d", + "0x4d694DE17246086d6451D732Ea8EA2a9a76dC997", + "0x5d51B4c1fb0c67d0e1274EC96c1B895F45505a3D", + "0x13cF11F76a08214A826355a1C8d661E41EA7Bf97", + "0xBD2369a9535751004617bC47cB0BF8Ea5c35Ed7C", + "0x83070c799c0d41526D4c71e462557CdbB2C750AC", + "0x7365666466f97E8aBBEE8900925521e0469A1f25", + "0xaa58F0fC9eddeFBef9E4C1c3e81f8cA5f22b9B8e" ], "bls_pub_keys": [ - "0x87c2078cddf068a6d3b1a4d8e6ff7d648a8ad500f5cba2250decb1af524a4fb68b73309edc734eecceaa3088be9e9c44", - "0xad98df6893d6ef125d2afed15132408e39f1731661227d78e3baa80528fbace455f677bda9a2e973bfc4bb97097c1c14", - "0xa3b3aa9700e02c5682dbce12238a22fbbc3d23365894cf9178880968ec844c6ed4b8dddaf0e4902572556635ec35c2da", - "0x8b314d4920ea7bfa7ec08876831f707990337385772d43f977e4f4e7a17df709a81bfa1d39c4d3789968a411b1073fa5", - "0x8bf2c03799022c607e1205926b83d48cb8413bc12884b98de0adaf762b7bb3f49e0890620161ffd7ed4d714d24a1c56d", - "0x94e963876db2dcc74d59b90c5c7e40f0db51805368d74155d8ded81c5f2952ec9d2251711699118051f519b0dc9b73e7", - "0x9818a58143a317a0dfc19c606879474753bb0134f3d71bc86017d0c6dd468ea135806b0d298141d2662d0bbe7ded7621", - "0xa8d29885c820ff400e9044ff22196b22ddd50062e3418fdc77c5f95627544526125e662337083c4a92eacb15df302f17", - "0xa2480af2948d674515a0bc8c1b6c19b4fccd03cb3e0bffd0e43e25099b842e64c834bc37d024ba5746308b44f374348a" + "0xb80423265ab0f4beeb192c5d62a3cee0502adb86b87885a60da2113aa8ad82200a5a019fd5a06d0e032054193916e7f1", + "0xaa6882d7d2f190340fbe30582cca08e23f959c4c5c876e52d9e37ca5b560e143e23e7f45b8209fef4821b7624cd4b3cf", + "0x909416e9d5408a6917376bca0ce7192b5e03250a1776ecdaaea5e6a53a10c911631a06864a94e5e7d451c3c8e1c2c556", + "0xada0ff2c7aa7b25d9663cdae239b1bb72672641960208a7ceca4cae576db1b5dc54a1eb6ccbade07cb5a688879045caa", + "0xabbebad3349b4d200b79c238570bf65199b1e4b302f2b38f9a132b145bdc64a24f5e83df0e104152daf08248a5973566", + "0xb73537f19bd9217eb1ac9b068a7c1dad51d4802eaba374e920356373dc1dd80c0d066aa58b00859a4de30446ab8b87a2", + "0x8e90789947f959b853fb9c4aebeaa0348b86490db30a96ae7fc1049fde94afaeba7059b30e65b697751cd17f0a0f0e11", + "0x8853b3a124aa5a59f3179009f633dfc0b322ca77c6f423b027c45b20c7bd6bb0de1f15cdaed6f9ff56562e93b6b70c2c", + "0xafeeb4ce8cdc76adb250dd1d2314fb4da2adc4f35584867f72a2fca47a35548ebd1d95ba8f11134440419c7fa62a25eb" ], "chain_id": 420 } -] +] \ No newline at end of file diff --git a/script/output/deployed_lgr.json b/script/output/deployed_lgr.json index c7ec986..b361b32 100644 --- a/script/output/deployed_lgr.json +++ b/script/output/deployed_lgr.json @@ -1,13 +1,13 @@ { "addresses": { - "lagrangeCommittee": "0x0AEd0dC7f53CB452A34A3Fe4d6a7E4Fdd110ed0f", - "lagrangeCommitteeImp": "0x9E6F4600a8C6c91EC5158D752824467B4c6B9c17", - "lagrangeService": "0xe509Dea686816C3c64ed4950dA54792a70A25239", - "lagrangeServiceImp": "0x2008D5344b209B4DE89e5ba218A097FFc6DcdA99", - "lagrangeServiceManager": "0x1387E373a9fe3c8DdF77ea9E66616678e5B9D918", - "lagrangeServiceManagerImp": "0xdE13D29456810eD75139240C2B4e5DfDab7A6f6f", - "proxyAdmin": "0x5bA2061A8ab6D0c61b3e29f5b49C37A5FDF4AE72", - "stakeManager": "0xFb7c846bD3285A9CC856eEa80792CAf51347befF", - "stakeManagerImp": "0xb5f6560d0F0323afF85fA33790BAdB99D65Aa613" + "lagrangeCommittee": "0xdf482e95c33BfBC10312554627162462b3986365", + "lagrangeCommitteeImp": "0x179F90FB3253e9Ad9Df15842410DdC551d29ee72", + "lagrangeService": "0x388Cd440D202d39376117A109fC47b2FFF6Cea43", + "lagrangeServiceImp": "0xD776a1eFf4637f7A5E4240553784e193C76e5264", + "lagrangeServiceManager": "0xfF0d1700a4C41aB483a396844FAb52315b3fE35b", + "lagrangeServiceManagerImp": "0xFDd715E746b0d00ED29c77c5D434aF4E3317fc49", + "proxyAdmin": "0xeCA41274474c68043983Ad5157f2befBEA463C9c", + "stakeManager": "0x996a01150d4e45F2A3344FAef9f68Ff10fAd5395", + "stakeManagerImp": "0x5993874B1C435085709EeDC4d58A70c98A46E9de" } } \ No newline at end of file diff --git a/script/output/deployed_mock.json b/script/output/deployed_mock.json index 2417386..ffeb551 100644 --- a/script/output/deployed_mock.json +++ b/script/output/deployed_mock.json @@ -1 +1 @@ -{"addresses":{"batchStorage":"0x2f947E51B9A7cF1d6651D0a568261673233ba42b"}} \ No newline at end of file +{"addresses":{"batchStorage":"0x904bb83A09D5E16c833938429F2Ed3AB3B3f332B"}} \ No newline at end of file diff --git a/script/output/deployed_poseidon.json b/script/output/deployed_poseidon.json index ed6a6d8..ca35562 100644 --- a/script/output/deployed_poseidon.json +++ b/script/output/deployed_poseidon.json @@ -1,8 +1,8 @@ { - "1": "0x189d4f39f158664185DBB101791F47485B5bE993", - "2": "0x91E333A3d61862B1FE976351cf0F3b30aff1D202", - "3": "0xF2740f6A6333c7B405aD7EfC68c74adAd83cC30D", - "4": "0x3074DB604785eEeC6c4Fadf4226137024764faD5", - "5": "0x6Bf0fF4eBa00E3668c0241bb1C622CDBFE55bbE0", - "6": "0x0E7C083DEEdB9a0EA3e06A316148fc110F7Fe49a" + "1": "0x45d2Af545d57047150Bf66F219BB832cf6376411", + "2": "0x761dFA9E39F1246493a5A71Ab2C07896cc198F4a", + "3": "0x4d6213Ea2C5fAc711bec91b61f882B3be485cFEa", + "4": "0x262cD7Ecb176d73dD277EF195c08aE8b232ccE58", + "5": "0x106C13B2310178CF77A0e75d7022232086dcE400", + "6": "0x0D161C95585D8Fc1c510724095CDB746fc8a3a58" } \ No newline at end of file diff --git a/script/output/deployed_weth9.json b/script/output/deployed_weth9.json index 7eeab6b..d669126 100644 --- a/script/output/deployed_weth9.json +++ b/script/output/deployed_weth9.json @@ -1 +1 @@ -{"WETH9":"0xbB9dDB1020F82F93e45DA0e2CFbd27756DA36956"} \ No newline at end of file +{"WETH9":"0x1D7Acca2751281Bd27d8254fC2fCd71a5243626c"} \ No newline at end of file diff --git a/src/interfaces/ILagrangeCommittee.sol b/src/interfaces/ILagrangeCommittee.sol index 375147f..f525778 100644 --- a/src/interfaces/ILagrangeCommittee.sol +++ b/src/interfaces/ILagrangeCommittee.sol @@ -4,14 +4,12 @@ pragma solidity ^0.8.12; struct OperatorStatus { uint256 amount; bytes blsPubKey; - uint32 serveUntilBlock; - uint32 chainID; bool slashed; -} - -struct OperatorUpdate { - address operator; - uint8 updateType; + uint32 serveUntilBlock; + uint32[] subscribedChains; + uint256 unsubscribedBlockNumber; + // ChainID => Block Number which can be unsubscribable + mapping(uint32 => uint256) unsubscribedChains; } interface ILagrangeCommittee { @@ -29,17 +27,23 @@ interface ILagrangeCommittee { function getServeUntilBlock(address operator) external returns (uint32); - function setSlashed(address operator) external; - function getSlashed(address operator) external returns (bool); function getCommittee(uint32 chainID, uint256 blockNumber) external returns (CommitteeData memory, uint256); - function addOperator(address operator, bytes memory blsPubKey, uint32 chainID, uint32 serveUntilBlock) external; + function addOperator(address operator, bytes memory blsPubKey, uint32 serveUntilBlock) external; + + function freezeOperator(address operator) external; function isLocked(uint32 chainID) external returns (bool, uint256); - function updateOperator(OperatorUpdate memory opUpdate) external; + function updateOperatorAmount(address operator) external; + + function subscribeChain(address operator, uint32 chainID) external; + + function unsubscribeChain(address operator, uint32 chainID) external; + + function isUnregisterable(address operator) external returns (bool, uint256); function update(uint32 chainID, uint256 epochNumber) external; } diff --git a/src/interfaces/ILagrangeService.sol b/src/interfaces/ILagrangeService.sol index 1cc0fe7..982beb4 100644 --- a/src/interfaces/ILagrangeService.sol +++ b/src/interfaces/ILagrangeService.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.12; interface ILagrangeService { - function register(uint32 chainID, bytes memory _blsPubKey, uint32 serveUntilBlock) external; + function register(bytes memory _blsPubKey, uint32 serveUntilBlock) external; function owner() external view returns (address); } diff --git a/src/protocol/LagrangeCommittee.sol b/src/protocol/LagrangeCommittee.sol index 0d76c3c..e8c7938 100644 --- a/src/protocol/LagrangeCommittee.sol +++ b/src/protocol/LagrangeCommittee.sol @@ -11,10 +11,6 @@ import "../interfaces/ILagrangeService.sol"; import "../interfaces/IVoteWeigher.sol"; contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, ILagrangeCommittee { - uint8 public constant UPDATE_TYPE_REGISTER = 1; - uint8 public constant UPDATE_TYPE_AMOUNT_CHANGE = 2; - uint8 public constant UPDATE_TYPE_UNREGISTER = 3; - ILagrangeService public immutable service; IVoteWeigher public immutable voteWeigher; @@ -111,18 +107,86 @@ contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, return operators[operator].serveUntilBlock; } - function updateOperator(OperatorUpdate memory opUpdate) external onlyServiceManager { - if (opUpdate.updateType == UPDATE_TYPE_AMOUNT_CHANGE) { - _updateAmount(opUpdate.operator, voteWeigher.weightOfOperator(opUpdate.operator, 1)); - } else if (opUpdate.updateType == UPDATE_TYPE_REGISTER) { - _registerOperator(opUpdate.operator); - } else if (opUpdate.updateType == UPDATE_TYPE_UNREGISTER) { - _unregisterOperator(opUpdate.operator); + // Adds address stake data and flags it for committee addition + function addOperator(address operator, bytes memory blsPubKey, uint32 serveUntilBlock) public onlyService { + uint96 stakeAmount = voteWeigher.weightOfOperator(operator, 1); + OperatorStatus storage opStatus = operators[operator]; + require(opStatus.amount == 0, "Operator is already registered."); + opStatus.amount = stakeAmount; + opStatus.serveUntilBlock = serveUntilBlock; + opStatus.blsPubKey = blsPubKey; + } + + function freezeOperator(address operator) external onlyServiceManager { + OperatorStatus storage opStatus = operators[operator]; + opStatus.slashed = true; + + for (uint256 i = 0; i < opStatus.subscribedChains.length; i++) { + _unregisterOperator(operator, opStatus.subscribedChains[i]); + } + } + + function updateOperatorAmount(address operator) external onlyServiceManager { + if (getSlashed(operator)) { + return; + } + for (uint256 i = 0; i < operators[operator].subscribedChains.length; i++) { + _updateAmount(operator, voteWeigher.weightOfOperator(operator, 1), operators[operator].subscribedChains[i]); + } + } + + function subscribeChain(address operator, uint32 chainID) external onlyService { + (bool locked,) = isLocked(chainID); + require(!locked, "The dedicated chain is locked."); + + OperatorStatus storage opStatus = operators[operator]; + require(!opStatus.slashed, "Operator is slashed."); + uint256 blockNumber = opStatus.unsubscribedChains[chainID]; + + if (blockNumber > 0 && blockNumber >= block.number) { + revert("The dedciated chain is while unsubscribing."); + } + + opStatus.subscribedChains.push(chainID); + _registerOperator(operator, chainID); + } + + function unsubscribeChain(address operator, uint32 chainID) external onlyService { + OperatorStatus storage opStatus = operators[operator]; + require(!opStatus.slashed, "Operator is slashed."); + + uint256 index; + uint256 subChainLength = opStatus.subscribedChains.length; + for (; index < subChainLength; index++) { + if (opStatus.subscribedChains[index] == chainID) { + break; + } + } + require(index < subChainLength, "The dedicated chain is not subscribed"); + + (bool locked, uint256 blockNumber) = isLocked(chainID); + require(!locked, "The dedicated chain is locked."); + + opStatus.subscribedChains[index] = opStatus.subscribedChains[subChainLength - 1]; + opStatus.subscribedChains.pop(); + + opStatus.unsubscribedChains[chainID] = blockNumber; + if (blockNumber > opStatus.unsubscribedBlockNumber) { + opStatus.unsubscribedBlockNumber = blockNumber; } + + _unregisterOperator(operator, chainID); } - function setSlashed(address operator) external onlyService { - operators[operator].slashed = true; + function isUnregisterable(address operator) public view returns (bool, uint256) { + OperatorStatus storage opStatus = operators[operator]; + require(!opStatus.slashed, "Operator is slashed."); + + if (opStatus.subscribedChains.length > 0) { + return (false, 0); + } + + return (true, opStatus.unsubscribedBlockNumber); } function getSlashed(address operator) public view returns (bool) { @@ -173,15 +237,6 @@ contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, _updateCommittee(chainID, 0); } - // Adds address stake data and flags it for committee addition - function addOperator(address operator, bytes memory blsPubKey, uint32 chainID, uint32 serveUntilBlock) - public - onlyService - { - uint96 stakeAmount = voteWeigher.weightOfOperator(operator, 1); - operators[operator] = OperatorStatus(stakeAmount, blsPubKey, serveUntilBlock, chainID, false); - } - // Checks if a chain's committee is updatable at a given block function isUpdatable(uint32 chainID, uint256 epochNumber) public view returns (bool) { uint256 epochEnd = epochNumber * committeeParams[chainID].duration + committeeParams[chainID].startBlock; @@ -223,9 +278,7 @@ contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, emit UpdateCommittee(chainID, bytes32(committees[chainID][nextEpoch].root)); } - function _registerOperator(address operator) internal { - uint32 chainID = operators[operator].chainID; - + function _registerOperator(address operator, uint32 chainID) internal { totalVotingPower[chainID] += operators[operator].amount; uint32 leafIndex = uint32(committeeAddrs[chainID].length); committeeAddrs[chainID].push(operator); @@ -238,8 +291,7 @@ contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, _updateTreeByIndex(chainID, leafIndex); } - function _updateAmount(address operator, uint256 updatedAmount) internal { - uint32 chainID = operators[operator].chainID; + function _updateAmount(address operator, uint256 updatedAmount, uint32 chainID) internal { uint256 leafIndex = committeeLeavesMap[chainID][operator]; committeeNodes[chainID][0][leafIndex] = getLeafHash(operator); totalVotingPower[chainID] -= operators[operator].amount; @@ -250,9 +302,7 @@ contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, _updateTreeByIndex(chainID, leafIndex); } - function _unregisterOperator(address operator) internal { - uint32 chainID = operators[operator].chainID; - + function _unregisterOperator(address operator, uint32 chainID) internal { totalVotingPower[chainID] -= operators[operator].amount; uint32 leafIndex = uint32(committeeLeavesMap[chainID][operator]); uint256 lastIndex = committeeAddrs[chainID].length - 1; @@ -296,7 +346,7 @@ contract LagrangeCommittee is Initializable, OwnableUpgradeable, HermezHelpers, function getLeafHash(address opAddr) public view returns (uint256) { uint96[11] memory slices; - OperatorStatus memory opStatus = operators[opAddr]; + OperatorStatus storage opStatus = operators[opAddr]; for (uint256 i = 0; i < 8; i++) { bytes memory addr = new bytes(12); for (uint256 j = 0; j < 12; j++) { diff --git a/src/protocol/LagrangeService.sol b/src/protocol/LagrangeService.sol index 62de7f7..a4428fa 100644 --- a/src/protocol/LagrangeService.sol +++ b/src/protocol/LagrangeService.sol @@ -45,26 +45,30 @@ contract LagrangeService is Initializable, OwnableUpgradeable, ILagrangeService, } /// Add the operator to the service. - // Only unfractinalized WETH strategy shares assumed for stake amount - function register(uint32 chainID, bytes memory _blsPubKey, uint32 serveUntilBlock) external { - // NOTE: Please ensure that the order of the following two lines remains unchanged - (bool locked,) = committee.isLocked(chainID); - require(!locked, "The related chain is in the freeze period"); - + function register(bytes memory _blsPubKey, uint32 serveUntilBlock) external { require(_blsPubKey.length == 96, "LagrangeService: Inappropriately preformatted BLS public key."); - committee.addOperator(msg.sender, _blsPubKey, chainID, serveUntilBlock); + committee.addOperator(msg.sender, _blsPubKey, serveUntilBlock); + serviceManager.recordFirstStakeUpdate(msg.sender, serveUntilBlock); emit OperatorRegistered(msg.sender, serveUntilBlock); } - /// deregister the operator from the service. - function deregister(uint32 chainID) external { - (bool locked, uint256 epochEnd) = committee.isLocked(chainID); - require(!locked, "The related chain is in the freeze period"); + /// Subscribe the dedicated chain. + function subscribe(uint32 chainID) external { + committee.subscribeChain(msg.sender, chainID); + } - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(msg.sender, uint32(epochEnd)); + function unsubscribe(uint32 chainID) external { + committee.unsubscribeChain(msg.sender, chainID); + } + + /// deregister the operator from the service. + function deregister() external { + (bool possible, uint256 unsubscribeBlockNumber) = committee.isUnregisterable(msg.sender); + require(possible, "The operator is not able to deregister"); + serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(msg.sender, uint32(unsubscribeBlockNumber)); } /// upload the evidence to punish the operator. @@ -160,7 +164,6 @@ contract LagrangeService is Initializable, OwnableUpgradeable, ILagrangeService, /// Slash the given operator function _freezeOperator(address operator) internal { - committee.setSlashed(operator); serviceManager.freezeOperator(operator); emit OperatorSlashed(operator); diff --git a/src/protocol/LagrangeServiceManager.sol b/src/protocol/LagrangeServiceManager.sol index b6d72d4..3834889 100644 --- a/src/protocol/LagrangeServiceManager.sol +++ b/src/protocol/LagrangeServiceManager.sol @@ -5,15 +5,11 @@ import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import {IServiceManager} from "../interfaces/IServiceManager.sol"; -import {ILagrangeCommittee, OperatorUpdate} from "../interfaces/ILagrangeCommittee.sol"; +import {ILagrangeCommittee} from "../interfaces/ILagrangeCommittee.sol"; import {ILagrangeService} from "../interfaces/ILagrangeService.sol"; import {IStakeManager} from "../interfaces/IStakeManager.sol"; contract LagrangeServiceManager is Initializable, OwnableUpgradeable, IServiceManager { - uint8 public constant UPDATE_TYPE_REGISTER = 1; - uint8 public constant UPDATE_TYPE_AMOUNT_CHANGE = 2; - uint8 public constant UPDATE_TYPE_UNREGISTER = 3; - IStakeManager public immutable slasher; ILagrangeCommittee public immutable committee; ILagrangeService public immutable service; @@ -39,19 +35,18 @@ contract LagrangeServiceManager is Initializable, OwnableUpgradeable, IServiceMa // slash the given operator function freezeOperator(address operator) external onlyService { - committee.updateOperator(OperatorUpdate({operator: operator, updateType: UPDATE_TYPE_UNREGISTER})); + committee.freezeOperator(operator); slasher.freezeOperator(operator); } function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external onlyService { - committee.updateOperator(OperatorUpdate({operator: operator, updateType: UPDATE_TYPE_REGISTER})); slasher.recordFirstStakeUpdate(operator, serveUntilBlock); } function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external { - committee.updateOperator(OperatorUpdate({operator: operator, updateType: UPDATE_TYPE_AMOUNT_CHANGE})); + committee.updateOperatorAmount(operator); slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, prevElement); } @@ -59,7 +54,6 @@ contract LagrangeServiceManager is Initializable, OwnableUpgradeable, IServiceMa external onlyService { - committee.updateOperator(OperatorUpdate({operator: operator, updateType: UPDATE_TYPE_UNREGISTER})); slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock); } diff --git a/test/e2e/test_committee.js b/test/e2e/test_committee.js index 3595885..ca3b562 100644 --- a/test/e2e/test_committee.js +++ b/test/e2e/test_committee.js @@ -9,7 +9,7 @@ const deployedAddresses = require('../../script/output/deployed_lgr.json'); require('dotenv').config(); const address = '0x6E654b122377EA7f592bf3FD5bcdE9e8c1B1cEb9'; -const tokenAddress = '0xbB9dDB1020F82F93e45DA0e2CFbd27756DA36956'; +const tokenAddress = '0x1D7Acca2751281Bd27d8254fC2fCd71a5243626c'; const privKey = accounts[address]; const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(privKey, provider); @@ -43,16 +43,20 @@ stake.operatorStakes(tokenAddress, address).then((stake) => { const arbChainID = 1337; const optChainID = 420; -contract.getCommittee(optChainID, 1000).then((current) => { +contract.getCommittee(optChainID, 5000).then((current) => { console.log('Opt Current committee: ', current[0]); console.log('Opt Next committee: ', current[1]); }); -contract.getCommittee(arbChainID, 1000).then((current) => { +contract.getCommittee(arbChainID, 4677).then((current) => { console.log('Arb Current committee: ', current[0]); console.log('Arb Next committee: ', current[1]); }); +contract.committeeParams(arbChainID).then((params) => { + console.log('Arb params: ', params); +}); + contract.operators(address).then((op) => { console.log(op); }); diff --git a/test/foundry/LagrangeDeployer.t.sol b/test/foundry/LagrangeDeployer.t.sol index aad8130..cd07e6a 100644 --- a/test/foundry/LagrangeDeployer.t.sol +++ b/test/foundry/LagrangeDeployer.t.sol @@ -148,8 +148,9 @@ contract LagrangeDeployer is Test { vm.roll(START_EPOCH); vm.startPrank(vm.addr(1)); - // register chain + // register chains lagrangeCommittee.registerChain(CHAIN_ID, EPOCH_PERIOD, FREEZE_DURATION); + lagrangeCommittee.registerChain(CHAIN_ID + 1, EPOCH_PERIOD * 2, FREEZE_DURATION * 2); // register token multiplier stakeManager.setTokenMultiplier(address(token), 1e9); // register quorum diff --git a/test/foundry/RegisterOperator.t.sol b/test/foundry/RegisterOperator.t.sol index 8db194c..5916715 100644 --- a/test/foundry/RegisterOperator.t.sol +++ b/test/foundry/RegisterOperator.t.sol @@ -20,17 +20,31 @@ contract RegisterOperatorTest is LagrangeDeployer { // register operator vm.roll(START_EPOCH + EPOCH_PERIOD - FREEZE_DURATION - 1); - lagrangeService.register(CHAIN_ID, blsPubKey, type(uint32).max); + lagrangeService.register(blsPubKey, type(uint32).max); + lagrangeService.subscribe(CHAIN_ID); + lagrangeService.subscribe(CHAIN_ID + 1); - // deregister operator + // unsubscribe operator vm.roll(START_EPOCH + EPOCH_PERIOD - FREEZE_DURATION); - lagrangeService.deregister(CHAIN_ID); + lagrangeService.unsubscribe(CHAIN_ID); + + // deregister operator + vm.expectRevert("The operator is not able to deregister"); + lagrangeService.deregister(); + + // unsubscribe operator + vm.roll(START_EPOCH + EPOCH_PERIOD * 2 - FREEZE_DURATION * 2); + lagrangeService.unsubscribe(CHAIN_ID + 1); + + // deregister operator + lagrangeService.deregister(); // withdraw tokens from stake manager + vm.roll(START_EPOCH + EPOCH_PERIOD * 2); vm.expectRevert("Stake is locked"); stakeManager.withdraw(address(token), amount); - vm.roll(START_EPOCH + EPOCH_PERIOD + 1); + vm.roll(START_EPOCH + EPOCH_PERIOD * 2 + 1); stakeManager.withdraw(address(token), amount); vm.stopPrank(); @@ -44,20 +58,24 @@ contract RegisterOperatorTest is LagrangeDeployer { // it should fail because the committee is in freeze period vm.roll(START_EPOCH + EPOCH_PERIOD - FREEZE_DURATION + 1); - vm.expectRevert("The related chain is in the freeze period"); - lagrangeService.register(CHAIN_ID, blsPubKey, type(uint32).max); + lagrangeService.register(blsPubKey, type(uint32).max); + vm.expectRevert("The dedicated chain is locked."); + lagrangeService.subscribe(CHAIN_ID); + // register operator vm.roll(START_EPOCH + EPOCH_PERIOD - FREEZE_DURATION); - lagrangeService.register(CHAIN_ID, blsPubKey, type(uint32).max); + lagrangeService.register(blsPubKey, type(uint32).max); + lagrangeService.subscribe(CHAIN_ID); // deregister operator should fail due to the freeze period vm.roll(START_EPOCH + EPOCH_PERIOD - FREEZE_DURATION + 1); - vm.expectRevert("The related chain is in the freeze period"); - lagrangeService.deregister(CHAIN_ID); + vm.expectRevert("The dedicated chain is locked."); + lagrangeService.unsubscribe(CHAIN_ID); // deregister operator vm.roll(START_EPOCH + EPOCH_PERIOD - FREEZE_DURATION); - lagrangeService.deregister(CHAIN_ID); + lagrangeService.unsubscribe(CHAIN_ID); + lagrangeService.deregister(); vm.stopPrank(); } diff --git a/test/hardhat/LagrangeCommittee.test.js b/test/hardhat/LagrangeCommittee.test.js index 4850e43..7b07603 100644 --- a/test/hardhat/LagrangeCommittee.test.js +++ b/test/hardhat/LagrangeCommittee.test.js @@ -141,10 +141,9 @@ describe('LagrangeCommittee', function () { addr, '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + j, - chainid, 4294967295, ); - tx = await committee.updateOperator({ operator: addr, updateType: 1 }); + tx = await committee.subscribeChain(addr, chainid); rec = await tx.wait(); croot = await committee.committees(chainid, 1); @@ -171,10 +170,7 @@ describe('LagrangeCommittee', function () { for (j = 0; j < addrs.length; j++) { index = 3 - j; console.log('Removing operator ' + addrs[index] + '...'); - tx = await committee.updateOperator({ - operator: addrs[index], - updateType: 3, - }); + tx = await committee.unsubscribeChain(addrs[index], chainid); rec = await tx.wait(); } } @@ -192,13 +188,8 @@ describe('LagrangeCommittee', function () { const newPubKey = '0x' + Gx + Gy; const address = operators[0].operators[0]; - await committee.addOperator( - address, - newPubKey, - operators[0].chain_id, - serveUntilBlock, - ); - await committee.updateOperator({ operator: address, updateType: 1 }); + await committee.addOperator(address, newPubKey, serveUntilBlock); + await committee.subscribeChain(address, operators[0].chain_id); await committee.registerChain(operators[0].chain_id, 10000, 1000); const chunks = []; @@ -256,13 +247,8 @@ describe('LagrangeCommittee', function () { const Gy = pubKey.toAffine()[1].value.toString(16).padStart(96, '0'); const newPubKey = '0x' + Gx + Gy; - await committee.addOperator( - op, - newPubKey, - operators[0].chain_id, - serveUntilBlock, - ); - await committee.updateOperator({ operator: op, updateType: 1 }); + await committee.addOperator(op, newPubKey, serveUntilBlock); + await committee.subscribeChain(op, operators[0].chain_id); } await committee.registerChain(operators[0].chain_id, 10000, 1000); @@ -300,7 +286,7 @@ describe('LagrangeCommittee', function () { count /= 2; } console.log(leaves[0].toString(16)); - expect('0x' + leaves[0].toString(16)).to.equal( + expect('0x' + leaves[0].toString(16).padStart(64, '0')).to.equal( committeeRoot.currentCommittee.root.toHexString(), ); }); @@ -317,34 +303,29 @@ describe('LagrangeCommittee', function () { const Gy = pubKey.toAffine()[1].value.toString(16).padStart(96, '0'); const newPubKey = '0x' + Gx + Gy; - await committee.addOperator( - op, - newPubKey, - operators[0].chain_id, - serveUntilBlock, - ); - await committee.updateOperator({ operator: op, updateType: 1 }); + await committee.addOperator(op, newPubKey, serveUntilBlock); + await committee.subscribeChain(op, operators[0].chain_id); } - // unregister the first operator - await committee.updateOperator({ - operator: operators[0].operators[0], - updateType: 3, - }); - // unregister the last operator - await committee.updateOperator({ - operator: operators[0].operators[operators[0].operators.length - 2], - updateType: 3, - }); - // unregister the middle operator - await committee.updateOperator({ - operator: operators[0].operators[4], - updateType: 3, - }); - await committee.updateOperator({ - operator: operators[0].operators[5], - updateType: 3, - }); + // unsubscribe the first operator + await committee.unsubscribeChain( + operators[0].operators[0], + operators[0].chain_id, + ); + // unsubscribe the last operator + await committee.unsubscribeChain( + operators[0].operators[operators[0].operators.length - 2], + operators[0].chain_id, + ); + // unsubscribe the middle operator + await committee.unsubscribeChain( + operators[0].operators[4], + operators[0].chain_id, + ); + await committee.unsubscribeChain( + operators[0].operators[5], + operators[0].chain_id, + ); await committee.registerChain(operators[0].chain_id, 10000, 1000); let operatorCount = 5; @@ -381,7 +362,7 @@ describe('LagrangeCommittee', function () { count /= 2; } console.log(leaves[0].toString(16)); - expect('0x' + leaves[0].toString(16)).to.equal( + expect('0x' + leaves[0].toString(16).padStart(64, '0')).to.equal( committeeRoot.currentCommittee.root.toHexString(), ); }); diff --git a/util/deploy-register.js b/util/deploy-register.js index ac99550..b027be6 100644 --- a/util/deploy-register.js +++ b/util/deploy-register.js @@ -20,30 +20,52 @@ const convertBLSPubKey = (oldPubKey) => { return newPubKey; }; -operators.forEach(async (chain, k) => { - for (let index = 0; index < chain.operators.length; index++) { - const address = chain.operators[index]; - const privKey = accounts[address]; - const wallet = new ethers.Wallet(privKey, provider); - const contract = new ethers.Contract( - deployedAddresses.addresses.lagrangeService, - abi, - wallet, - ); - const nonce = await provider.getTransactionCount(address); +(async () => { + await Promise.all( + operators[0].operators.map(async (operator, index) => { + const privKey = accounts[operator]; + const wallet = new ethers.Wallet(privKey, provider); + const contract = new ethers.Contract( + deployedAddresses.addresses.lagrangeService, + abi, + wallet, + ); - const tx = await contract.register( - chain.chain_id, - convertBLSPubKey(chain.bls_pub_keys[index]), - uint32Max, - { + const tx = await contract.register( + convertBLSPubKey(operators[0].bls_pub_keys[index]), + uint32Max, + ); + console.log( + `Starting to register operator for address: ${operator} tx hash: ${tx.hash}`, + ); + const receipt = await tx.wait(); + console.log( + `Register Transaction was mined in block ${receipt.blockNumber}`, + ); + }), + ); + + operators.forEach(async (chain, k) => { + for (let index = 0; index < chain.operators.length; index++) { + const address = chain.operators[index]; + const privKey = accounts[address]; + const wallet = new ethers.Wallet(privKey, provider); + const contract = new ethers.Contract( + deployedAddresses.addresses.lagrangeService, + abi, + wallet, + ); + const nonce = await provider.getTransactionCount(address); + const tx = await contract.subscribe(chain.chain_id, { nonce: nonce + k, - }, - ); - console.log( - `Starting to register operator for address: ${address} tx hash: ${tx.hash}`, - ); - const receipt = await tx.wait(); - console.log(`Transaction was mined in block ${receipt.blockNumber}`); - } -}); + }); + console.log( + `Starting to subscribe operator for address: ${address} tx hash: ${tx.hash}`, + ); + const receipt = await tx.wait(); + console.log( + `Subscribe Transaction was mined in block ${receipt.blockNumber}`, + ); + } + }); +})(); diff --git a/util/generate-accounts.js b/util/generate-accounts.js index 59efcd4..8638a11 100644 --- a/util/generate-accounts.js +++ b/util/generate-accounts.js @@ -7,7 +7,7 @@ require('dotenv').config(); const DEFAULT_MNEMONIC = 'exchange holiday girl alone head gift unfair resist void voice people tobacco'; -const DEFAULT_NUM_ACCOUNTS = 150; +const DEFAULT_NUM_ACCOUNTS = 9; async function genBLSKey() { await bls.init(bls.BLS12_381); diff --git a/util/init-accounts.js b/util/init-accounts.js index 42740b4..513211f 100644 --- a/util/init-accounts.js +++ b/util/init-accounts.js @@ -6,7 +6,7 @@ require('dotenv').config(); const DEFAULT_MNEMONIC = 'exchange holiday girl alone head gift unfair resist void voice people tobacco'; -const DEFAULT_NUM_ACCOUNTS = 150; +const DEFAULT_NUM_ACCOUNTS = 20; async function main() { const currentProvider = new ethers.providers.JsonRpcProvider(