From 46cad1b496e62eb426b14f3d02f24b454ee71d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBuk?= Date: Thu, 19 Oct 2023 17:33:14 +0200 Subject: [PATCH] BLOCKCHAIN-234 - bridge contracts - cap the supply limit that can be set by admin --- eth-bridge/contracts/script/Deploy.s.sol | 10 +++-- .../contracts/script/UpgradeBridgesToV2.s.sol | 30 ++++++++++++++ eth-bridge/contracts/src/Bridge.sol | 30 +++++++++++++- eth-bridge/contracts/test/Bridge.t.sol | 39 ++++++++----------- .../contracts/test/VoteFeeCompare.t.sol | 5 ++- 5 files changed, 85 insertions(+), 29 deletions(-) create mode 100644 eth-bridge/contracts/script/UpgradeBridgesToV2.s.sol diff --git a/eth-bridge/contracts/script/Deploy.s.sol b/eth-bridge/contracts/script/Deploy.s.sol index a3e86f49d..0749019be 100644 --- a/eth-bridge/contracts/script/Deploy.s.sol +++ b/eth-bridge/contracts/script/Deploy.s.sol @@ -40,8 +40,9 @@ contract Deploy is Script { fee, 30_000_000_000_000_000, // max burst mint 60_000_000_000_000, // rate limit counter decay - 300_000_000_000_000_000, // max total supply - 30_000_000_000_000 // min transfer + 300_000_000_000_000_000, // supply limit + 30_000_000_000_000, // min transfer + 3_000_000_000_000_000_000 // max supply limit ) ) ); @@ -67,8 +68,9 @@ contract Deploy is Script { fee, 10_000_000_000_000_000, // max burst mint 20_000_000_000_000, // rate limit counter decay - 100_000_000_000_000_000, // max total supply - 10_000_000_000_000 // min transfer + 100_000_000_000_000_000, // supply limit + 10_000_000_000_000, // min transfer + 1_000_000_000_000_000_000 // max supply limit ) ) ); diff --git a/eth-bridge/contracts/script/UpgradeBridgesToV2.s.sol b/eth-bridge/contracts/script/UpgradeBridgesToV2.s.sol new file mode 100644 index 000000000..78a58fbff --- /dev/null +++ b/eth-bridge/contracts/script/UpgradeBridgesToV2.s.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../src/Bridge.sol"; + +contract UpgradeBridgesToV2 is Script { + function run() external { + vm.startBroadcast(); + Bridge lldProxy = Bridge(vm.envAddress("LLDBridgeProxy")); + Bridge llmProxy = Bridge(vm.envAddress("LLMBridgeProxy")); + + Bridge newBridgeImpl = new Bridge(); + + lldProxy.upgradeToAndCall( + address(newBridgeImpl), + abi.encodeCall( + Bridge.initializeV2, + (3_000_000_000_000_000_000) // max supply limit of 3M LLD, admin can lower it + ) + ); + llmProxy.upgradeToAndCall( + address(newBridgeImpl), + abi.encodeCall( + Bridge.initializeV2, + (1_000_000_000_000_000_000) // max supply limit of 1M LLM, admin can lower it + ) + ); + } +} diff --git a/eth-bridge/contracts/src/Bridge.sol b/eth-bridge/contracts/src/Bridge.sol index 5553c2bfc..1e0022834 100644 --- a/eth-bridge/contracts/src/Bridge.sol +++ b/eth-bridge/contracts/src/Bridge.sol @@ -94,6 +94,8 @@ contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, Bri RateLimitParameters public rateLimit; // 2x uint256 /// Minimum transfer - only applied for burns uint256 public minTransfer; + /// Initial supply limit - admin can't increase supply limit above this value + uint256 public maxSupplyLimit; constructor() { _disableInitializers(); @@ -108,7 +110,23 @@ contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, Bri /// @param decayRate initial `rateLimit.decayRate` /// @param supplyLimit_ initial `supplyLimit` /// @param minTransfer_ initial `minTransfer` + /// @param maxSupplyLimit_ maximum `supplyLimit` that can be set by admin function initialize( + WrappedToken token_, + uint32 votesRequired_, + uint256 mintDelay_, + uint256 fee_, + uint256 counterLimit, + uint256 decayRate, + uint256 supplyLimit_, + uint256 minTransfer_, + uint256 maxSupplyLimit_ + ) external { + initializeV1(token_, votesRequired_, mintDelay_, fee_, counterLimit, decayRate, supplyLimit_, minTransfer_); + initializeV2(maxSupplyLimit_); + } + + function initializeV1( WrappedToken token_, uint32 votesRequired_, uint256 mintDelay_, @@ -117,7 +135,7 @@ contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, Bri uint256 decayRate, uint256 supplyLimit_, uint256 minTransfer_ - ) public initializer { + ) internal initializer { __AccessControl_init(); __UUPSUpgradeable_init(); @@ -134,6 +152,12 @@ contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, Bri _grantRole(SUPER_ADMIN_ROLE, msg.sender); } + /// Reinitializer from v1 to v2. Should be used in the same tx as upgrade + /// @param maxSupplyLimit_ maximum `supplyLimit` that can be set by admin + function initializeV2(uint256 maxSupplyLimit_) public reinitializer(2) { + maxSupplyLimit = maxSupplyLimit_; + } + /// Adding special users. See role docs on info who can grant each role /// @param role Role to grant /// @param account Account to grant the role to @@ -361,7 +385,11 @@ contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, Bri /// Set max circulating token supply /// @param supplyLimit_ new supply limit /// @dev Only addresses with SUPER_ADMIN_ROLE can call this + /// @dev Reverts with `InvalidArgument` if `supplyLimit_` is greater than original limit set in constructor function setSupplyLimit(uint256 supplyLimit_) public onlyRole(SUPER_ADMIN_ROLE) { + if (supplyLimit_ > maxSupplyLimit) { + revert InvalidArgument(); + } supplyLimit = supplyLimit_; } diff --git a/eth-bridge/contracts/test/Bridge.t.sol b/eth-bridge/contracts/test/Bridge.t.sol index 91cb6af1e..ed1471280 100644 --- a/eth-bridge/contracts/test/Bridge.t.sol +++ b/eth-bridge/contracts/test/Bridge.t.sol @@ -30,30 +30,19 @@ contract BridgeTest is Test, BridgeEvents { token = WrappedToken( address( new ERC1967Proxy( - address(tokenImpl), - abi.encodeCall( - WrappedToken.initialize, - ("Liberland Merits", "LLM") - ) + address(tokenImpl), + abi.encodeCall(WrappedToken.initialize, ("Liberland Merits", "LLM")) ) ) ); + bridge = Bridge( address( new ERC1967Proxy( address(bridgeImpl), abi.encodeCall( Bridge.initialize, - ( - token, - 2, - 10, - 4, - 1000, - 10, - 650, - 0 - ) + (token, 2, 10, 4, 1000, 10, 1_000_000, 0, 1_000_000) ) ) ) @@ -269,6 +258,8 @@ contract BridgeTest is Test, BridgeEvents { } function testMintRespectsMaxIssuanceLimit() public { + // we start with 500 already minted + bridge.setSupplyLimit(650); vm.startPrank(alice); bridge.voteMint(receipt1, 1, 149, alice); bridge.voteMint(receipt2, 1, 2, alice); @@ -651,7 +642,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitAllowsSingleTxAtLimit() public { - bridge.setSupplyLimit(99999); vm.prank(bob); bridge.voteMint(receipt1, 1, 1000, alice); vm.startPrank(alice); @@ -662,7 +652,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitAllowsMultiTxAtLimit() public { - bridge.setSupplyLimit(99999); vm.startPrank(bob); bridge.voteMint(receipt1, 1, 500, alice); bridge.voteMint(receipt2, 1, 500, alice); @@ -678,7 +667,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitAllowsUsingDecayedAmountRightAway() public { - bridge.setSupplyLimit(99999); vm.startPrank(bob); bridge.voteMint(receipt1, 1, 1000, alice); bridge.voteMint(receipt2, 1, 10, alice); @@ -695,7 +683,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitGoesToZeroAfterWholeWindow() public { - bridge.setSupplyLimit(99999); vm.startPrank(bob); bridge.voteMint(receipt1, 1, 1000, alice); bridge.voteMint(receipt2, 1, 1000, alice); @@ -712,7 +699,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitPreventsSingleBigMint() public { - bridge.setSupplyLimit(99999); vm.prank(bob); bridge.voteMint(receipt1, 1, 1001, alice); vm.prank(alice); @@ -724,7 +710,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitPreventsMultiBigMint() public { - bridge.setSupplyLimit(99999); vm.startPrank(bob); bridge.voteMint(receipt1, 1, 501, alice); bridge.voteMint(receipt2, 1, 500, alice); @@ -742,7 +727,6 @@ contract BridgeTest is Test, BridgeEvents { } function testRateLimitRespectsDecay() public { - bridge.setSupplyLimit(99999); vm.startPrank(bob); bridge.voteMint(receipt1, 1, 1000, alice); bridge.voteMint(receipt2, 1, 500, alice); @@ -859,4 +843,15 @@ contract BridgeTest is Test, BridgeEvents { bridge.setMinTransfer(9); bridge.burn(9, substrate1); } + + function testSetSupplyLimitCantExceedMaxSupplyLimit() public { + uint256 maxLimit = bridge.maxSupplyLimit(); + + bridge.setSupplyLimit(maxLimit - 2); + bridge.setSupplyLimit(maxLimit - 1); + bridge.setSupplyLimit(maxLimit); + + vm.expectRevert(InvalidArgument.selector); + bridge.setSupplyLimit(maxLimit + 1); + } } diff --git a/eth-bridge/contracts/test/VoteFeeCompare.t.sol b/eth-bridge/contracts/test/VoteFeeCompare.t.sol index eb70bec01..cb2668df8 100644 --- a/eth-bridge/contracts/test/VoteFeeCompare.t.sol +++ b/eth-bridge/contracts/test/VoteFeeCompare.t.sol @@ -8,7 +8,7 @@ import "../src/Bridge.sol"; using stdStorage for StdStorage; -contract BridgeTest is Test, BridgeEvents { +contract VoteFeeCompare is Test, BridgeEvents { WrappedToken public token; Bridge public bridge; @@ -52,7 +52,8 @@ contract BridgeTest is Test, BridgeEvents { 1000, 10, 650, - 0 + 0, + 650 ) ) )