From a0dfd3a84a946f8812d33a89ac3ee0bde15b4cc0 Mon Sep 17 00:00:00 2001 From: Rens Rooimans Date: Wed, 30 Oct 2024 00:00:48 +0100 Subject: [PATCH] add ownable2step (#14989) --- contracts/foundry.toml | 2 +- contracts/gas-snapshots/shared.gas-snapshot | 58 +++++++------ .../v0.8/shared/access/AuthorizedCallers.sol | 2 +- .../src/v0.8/shared/access/Ownable2Step.sol | 84 ++++++++++++++++++ .../shared/access/Ownable2StepMsgSender.sol | 9 ++ contracts/src/v0.8/shared/test/BaseTest.t.sol | 2 +- .../test/access/AuthorizedCallers.t.sol | 2 +- .../shared/test/access/Ownable2Step.t.sol | 87 +++++++++++++++++++ .../shared/test/call/CallWithExactGas.t.sol | 2 +- .../test/call/CallWithExactGasHelper.sol | 2 +- .../enumerable/EnumerableMapAddresses.t.sol | 2 +- .../test/token/ERC677/BurnMintERC677.t.sol | 2 +- 12 files changed, 221 insertions(+), 33 deletions(-) create mode 100644 contracts/src/v0.8/shared/access/Ownable2Step.sol create mode 100644 contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol create mode 100644 contracts/src/v0.8/shared/test/access/Ownable2Step.t.sol diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 781e875eb6..e1399964dd 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -95,7 +95,7 @@ test = 'src/v0.8/transmission/test' optimizer_runs = 1_000_000 src = 'src/v0.8/shared' test = 'src/v0.8/shared/test' -solc_version = '0.8.19' +solc_version = '0.8.24' # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/contracts/gas-snapshots/shared.gas-snapshot b/contracts/gas-snapshots/shared.gas-snapshot index dda850089c..e2163b9981 100644 --- a/contracts/gas-snapshots/shared.gas-snapshot +++ b/contracts/gas-snapshots/shared.gas-snapshot @@ -1,15 +1,15 @@ -AuthorizedCallers_applyAuthorizedCallerUpdates:test_AddAndRemove_Success() (gas: 125205) -AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyAdd_Success() (gas: 133100) -AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyCallableByOwner_Revert() (gas: 12350) -AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyRemove_Success() (gas: 45064) -AuthorizedCallers_applyAuthorizedCallerUpdates:test_RemoveThenAdd_Success() (gas: 57241) -AuthorizedCallers_applyAuthorizedCallerUpdates:test_SkipRemove_Success() (gas: 32121) -AuthorizedCallers_applyAuthorizedCallerUpdates:test_ZeroAddressNotAllowed_Revert() (gas: 64473) -AuthorizedCallers_constructor:test_ZeroAddressNotAllowed_Revert() (gas: 64473) -AuthorizedCallers_constructor:test_constructor_Success() (gas: 720513) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_AddAndRemove_Success() (gas: 125022) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyAdd_Success() (gas: 132980) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyCallableByOwner_Revert() (gas: 12356) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyRemove_Success() (gas: 45007) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_RemoveThenAdd_Success() (gas: 57121) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_SkipRemove_Success() (gas: 32064) +AuthorizedCallers_applyAuthorizedCallerUpdates:test_ZeroAddressNotAllowed_Revert() (gas: 64440) +AuthorizedCallers_constructor:test_ZeroAddressNotAllowed_Revert() (gas: 64440) +AuthorizedCallers_constructor:test_constructor_Success() (gas: 704809) BurnMintERC677_approve:testApproveSuccess() (gas: 55512) BurnMintERC677_approve:testInvalidAddressReverts() (gas: 10663) -BurnMintERC677_burn:testBasicBurnSuccess() (gas: 173939) +BurnMintERC677_burn:testBasicBurnSuccess() (gas: 172100) BurnMintERC677_burn:testBurnFromZeroAddressReverts() (gas: 47201) BurnMintERC677_burn:testExceedsBalanceReverts() (gas: 21841) BurnMintERC677_burn:testSenderNotBurnerReverts() (gas: 13359) @@ -21,7 +21,7 @@ BurnMintERC677_burnFromAlias:testBurnFromSuccess() (gas: 57949) BurnMintERC677_burnFromAlias:testExceedsBalanceReverts() (gas: 35880) BurnMintERC677_burnFromAlias:testInsufficientAllowanceReverts() (gas: 21869) BurnMintERC677_burnFromAlias:testSenderNotBurnerReverts() (gas: 13379) -BurnMintERC677_constructor:testConstructorSuccess() (gas: 1672809) +BurnMintERC677_constructor:testConstructorSuccess() (gas: 1672812) BurnMintERC677_decreaseApproval:testDecreaseApprovalSuccess() (gas: 31069) BurnMintERC677_grantMintAndBurnRoles:testGrantMintAndBurnRolesSuccess() (gas: 121324) BurnMintERC677_grantRole:testGrantBurnAccessSuccess() (gas: 53460) @@ -34,14 +34,14 @@ BurnMintERC677_mint:testSenderNotMinterReverts() (gas: 11195) BurnMintERC677_supportsInterface:testConstructorSuccess() (gas: 12476) BurnMintERC677_transfer:testInvalidAddressReverts() (gas: 10639) BurnMintERC677_transfer:testTransferSuccess() (gas: 42299) -CallWithExactGas__callWithExactGas:test_CallWithExactGasReceiverErrorSuccess() (gas: 67209) +CallWithExactGas__callWithExactGas:test_CallWithExactGasReceiverErrorSuccess() (gas: 65949) CallWithExactGas__callWithExactGas:test_CallWithExactGasSafeReturnDataExactGas() (gas: 18324) CallWithExactGas__callWithExactGas:test_NoContractReverts() (gas: 11559) CallWithExactGas__callWithExactGas:test_NoGasForCallExactCheckReverts() (gas: 15788) CallWithExactGas__callWithExactGas:test_NotEnoughGasForCallReverts() (gas: 16241) CallWithExactGas__callWithExactGas:test_callWithExactGasSuccess(bytes,bytes4) (runs: 256, μ: 15766, ~: 15719) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractExactGasSuccess() (gas: 20116) -CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractReceiverErrorSuccess() (gas: 67721) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractReceiverErrorSuccess() (gas: 66461) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes,bytes4) (runs: 256, μ: 16276, ~: 16229) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoContractSuccess() (gas: 12962) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoGasForCallExactCheckReturnFalseSuccess() (gas: 13005) @@ -75,20 +75,28 @@ EnumerableMapAddresses_tryGet:testBytes32TryGetSuccess() (gas: 94622) EnumerableMapAddresses_tryGet:testBytesTryGetSuccess() (gas: 96279) EnumerableMapAddresses_tryGet:testTryGetSuccess() (gas: 94893) OpStackBurnMintERC677_constructor:testConstructorSuccess() (gas: 1743649) -OpStackBurnMintERC677_interfaceCompatibility:testBurnCompatibility() (gas: 298649) +OpStackBurnMintERC677_interfaceCompatibility:testBurnCompatibility() (gas: 291393) OpStackBurnMintERC677_interfaceCompatibility:testMintCompatibility() (gas: 137957) OpStackBurnMintERC677_interfaceCompatibility:testStaticFunctionsCompatibility() (gas: 13781) OpStackBurnMintERC677_supportsInterface:testConstructorSuccess() (gas: 12752) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_EmptySubset_Reverts() (gas: 5460) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_EmptySuperset_Reverts() (gas: 4661) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_HasDuplicates_Reverts() (gas: 8265) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_NotASubset_Reverts() (gas: 12487) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubset() (gas: 4489) +Ownable2Step_acceptOwnership:test_acceptOwnership_MustBeProposedOwner_reverts() (gas: 10360) +Ownable2Step_acceptOwnership:test_acceptOwnership_success() (gas: 31088) +Ownable2Step_constructor:test_constructor_OwnerCannotBeZero_reverts() (gas: 35858) +Ownable2Step_constructor:test_constructor_success() (gas: 10428) +Ownable2Step_onlyOwner:test_onlyOwner_OnlyCallableByOwner_reverts() (gas: 10754) +Ownable2Step_onlyOwner:test_onlyOwner_success() (gas: 7506) +Ownable2Step_transferOwnership:test_transferOwnership_CannotTransferToSelf_reverts() (gas: 10501) +Ownable2Step_transferOwnership:test_transferOwnership_success() (gas: 30140) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_EmptySubset_Reverts() (gas: 5208) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_EmptySuperset_Reverts() (gas: 4535) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_HasDuplicates_Reverts() (gas: 7761) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_NotASubset_Reverts() (gas: 11668) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubset() (gas: 3922) SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubsetAndSuperset_Equal() (gas: 1464) SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SingleElementSubsetAndSuperset_NotEqual_Reverts() (gas: 6172) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SubsetEqualsSuperset_NoRevert() (gas: 8867) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SubsetLargerThanSuperset_Reverts() (gas: 16544) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SupersetHasDuplicates_Reverts() (gas: 9420) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_UnsortedSubset_Reverts() (gas: 7380) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_UnsortedSuperset_Reverts() (gas: 9600) -SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_ValidSubset_Success() (gas: 6490) \ No newline at end of file +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SubsetEqualsSuperset_NoRevert() (gas: 7859) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SubsetLargerThanSuperset_Reverts() (gas: 15410) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_SupersetHasDuplicates_Reverts() (gas: 8790) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_UnsortedSubset_Reverts() (gas: 7128) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_UnsortedSuperset_Reverts() (gas: 8970) +SortedSetValidationUtil_CheckIsValidUniqueSubsetTest:test__checkIsValidUniqueSubset_ValidSubset_Success() (gas: 5671) \ No newline at end of file diff --git a/contracts/src/v0.8/shared/access/AuthorizedCallers.sol b/contracts/src/v0.8/shared/access/AuthorizedCallers.sol index 93102d1a97..8c54d7bac8 100644 --- a/contracts/src/v0.8/shared/access/AuthorizedCallers.sol +++ b/contracts/src/v0.8/shared/access/AuthorizedCallers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; +pragma solidity ^0.8.4; import {OwnerIsCreator} from "./OwnerIsCreator.sol"; import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/src/v0.8/shared/access/Ownable2Step.sol b/contracts/src/v0.8/shared/access/Ownable2Step.sol new file mode 100644 index 0000000000..5eac576072 --- /dev/null +++ b/contracts/src/v0.8/shared/access/Ownable2Step.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {IOwnable} from "../interfaces/IOwnable.sol"; + +/// @notice A minimal contract that implements 2-step ownership transfer and nothing more. It's made to be minimal +/// to reduce the impact of the bytecode size on any contract that inherits from it. +contract Ownable2Step is IOwnable { + /// @notice The pending owner is the address to which ownership may be transferred. + address private s_pendingOwner; + /// @notice The owner is the current owner of the contract. + /// @dev The owner is the second storage variable so any implementing contract could pack other state with it + /// instead of the much less used s_pendingOwner. + address private s_owner; + + error OwnerCannotBeZero(); + error MustBeProposedOwner(); + error CannotTransferToSelf(); + error OnlyCallableByOwner(); + + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + + constructor(address newOwner, address pendingOwner) { + if (newOwner == address(0)) { + revert OwnerCannotBeZero(); + } + + s_owner = newOwner; + if (pendingOwner != address(0)) { + _transferOwnership(pendingOwner); + } + } + + /// @notice Get the current owner + function owner() public view override returns (address) { + return s_owner; + } + + /// @notice Allows an owner to begin transferring ownership to a new address. The new owner needs to call + /// `acceptOwnership` to accept the transfer before any permissions are changed. + /// @param to The address to which ownership will be transferred. + function transferOwnership(address to) public override onlyOwner { + _transferOwnership(to); + } + + /// @notice validate, transfer ownership, and emit relevant events + /// @param to The address to which ownership will be transferred. + function _transferOwnership(address to) private { + if (to == msg.sender) { + revert CannotTransferToSelf(); + } + + s_pendingOwner = to; + + emit OwnershipTransferRequested(s_owner, to); + } + + /// @notice Allows an ownership transfer to be completed by the recipient. + function acceptOwnership() external override { + if (msg.sender != s_pendingOwner) { + revert MustBeProposedOwner(); + } + + address oldOwner = s_owner; + s_owner = msg.sender; + s_pendingOwner = address(0); + + emit OwnershipTransferred(oldOwner, msg.sender); + } + + /// @notice validate access + function _validateOwnership() internal view { + if (msg.sender != s_owner) { + revert OnlyCallableByOwner(); + } + } + + /// @notice Reverts if called by anyone other than the contract owner. + modifier onlyOwner() { + _validateOwnership(); + _; + } +} diff --git a/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol b/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol new file mode 100644 index 0000000000..b3de785a70 --- /dev/null +++ b/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {Ownable2Step} from "./Ownable2Step.sol"; + +/// @notice Sets the msg.sender to be the owner of the contract and does not set a pending owner. +contract Ownable2StepMsgSender is Ownable2Step { + constructor() Ownable2Step(msg.sender, address(0)) {} +} diff --git a/contracts/src/v0.8/shared/test/BaseTest.t.sol b/contracts/src/v0.8/shared/test/BaseTest.t.sol index 4d8ef60eb2..b2e9afcd35 100644 --- a/contracts/src/v0.8/shared/test/BaseTest.t.sol +++ b/contracts/src/v0.8/shared/test/BaseTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import "forge-std/Test.sol"; diff --git a/contracts/src/v0.8/shared/test/access/AuthorizedCallers.t.sol b/contracts/src/v0.8/shared/test/access/AuthorizedCallers.t.sol index 34ae6848a4..54f74fbf16 100644 --- a/contracts/src/v0.8/shared/test/access/AuthorizedCallers.t.sol +++ b/contracts/src/v0.8/shared/test/access/AuthorizedCallers.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {AuthorizedCallers} from "../../access/AuthorizedCallers.sol"; import {BaseTest} from "../BaseTest.t.sol"; diff --git a/contracts/src/v0.8/shared/test/access/Ownable2Step.t.sol b/contracts/src/v0.8/shared/test/access/Ownable2Step.t.sol new file mode 100644 index 0000000000..0421d647c5 --- /dev/null +++ b/contracts/src/v0.8/shared/test/access/Ownable2Step.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {BaseTest} from "../BaseTest.t.sol"; +import {Ownable2Step} from "../../access/Ownable2Step.sol"; + +contract Ownable2Step_setup is BaseTest { + Ownable2StepHelper internal s_ownable2Step; + + function setUp() public override { + super.setUp(); + s_ownable2Step = new Ownable2StepHelper(OWNER, address(0)); + } +} + +contract Ownable2Step_constructor is Ownable2Step_setup { + function test_constructor_success() public view { + assertEq(OWNER, s_ownable2Step.owner()); + } + + function test_constructor_OwnerCannotBeZero_reverts() public { + vm.expectRevert(Ownable2Step.OwnerCannotBeZero.selector); + new Ownable2Step(address(0), address(0)); + } +} + +contract Ownable2Step_transferOwnership is Ownable2Step_setup { + function test_transferOwnership_success() public { + vm.expectEmit(); + emit Ownable2Step.OwnershipTransferRequested(OWNER, STRANGER); + + s_ownable2Step.transferOwnership(STRANGER); + + assertTrue(STRANGER != s_ownable2Step.owner()); + + vm.startPrank(STRANGER); + s_ownable2Step.acceptOwnership(); + } + + function test_transferOwnership_CannotTransferToSelf_reverts() public { + vm.expectRevert(Ownable2Step.CannotTransferToSelf.selector); + s_ownable2Step.transferOwnership(OWNER); + } +} + +contract Ownable2Step_acceptOwnership is Ownable2Step_setup { + function test_acceptOwnership_success() public { + s_ownable2Step.transferOwnership(STRANGER); + + assertTrue(STRANGER != s_ownable2Step.owner()); + + vm.startPrank(STRANGER); + + vm.expectEmit(); + emit Ownable2Step.OwnershipTransferred(OWNER, STRANGER); + + s_ownable2Step.acceptOwnership(); + + assertEq(STRANGER, s_ownable2Step.owner()); + } + + function test_acceptOwnership_MustBeProposedOwner_reverts() public { + vm.expectRevert(Ownable2Step.MustBeProposedOwner.selector); + s_ownable2Step.acceptOwnership(); + } +} + +contract Ownable2StepHelper is Ownable2Step { + constructor(address newOwner, address pendingOwner) Ownable2Step(newOwner, pendingOwner) {} + + function validateOwnership() external view { + _validateOwnership(); + } +} + +contract Ownable2Step_onlyOwner is Ownable2Step_setup { + function test_onlyOwner_success() public view { + s_ownable2Step.validateOwnership(); + } + + function test_onlyOwner_OnlyCallableByOwner_reverts() public { + vm.stopPrank(); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_ownable2Step.validateOwnership(); + } +} diff --git a/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol b/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol index 3623cb8b12..b432de58a5 100644 --- a/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol +++ b/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {CallWithExactGas} from "../../call/CallWithExactGas.sol"; import {CallWithExactGasHelper} from "./CallWithExactGasHelper.sol"; diff --git a/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol b/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol index 932315639b..0ba8602e08 100644 --- a/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol +++ b/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity ^0.8.4; import {CallWithExactGas} from "../../call/CallWithExactGas.sol"; diff --git a/contracts/src/v0.8/shared/test/enumerable/EnumerableMapAddresses.t.sol b/contracts/src/v0.8/shared/test/enumerable/EnumerableMapAddresses.t.sol index 097e79e372..689657105e 100644 --- a/contracts/src/v0.8/shared/test/enumerable/EnumerableMapAddresses.t.sol +++ b/contracts/src/v0.8/shared/test/enumerable/EnumerableMapAddresses.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "../BaseTest.t.sol"; import {EnumerableMapAddresses} from "../../enumerable/EnumerableMapAddresses.sol"; diff --git a/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol b/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol index 2815f99256..a778a1ada2 100644 --- a/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol +++ b/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {IBurnMintERC20} from "../../../token/ERC20/IBurnMintERC20.sol"; import {IERC677} from "../../../token/ERC677/IERC677.sol";