@layerzerolabs/utils-upgradeable-evm-contracts 0.2.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-lint.log +491 -0
- package/.turbo/turbo-test.log +1356 -0
- package/LICENSE +23 -0
- package/contracts/access/AccessControl2StepUpgradeable.sol +144 -0
- package/contracts/allowlist/AllowlistBaseUpgradeable.sol +184 -0
- package/contracts/allowlist/AllowlistRBACUpgradeable.sol +55 -0
- package/contracts/fee-accounting/FeeHandlerBaseUpgradeable.sol +69 -0
- package/contracts/fee-accounting/FeeHandlerRBACUpgradeable.sol +34 -0
- package/contracts/fee-config/FeeConfigBaseUpgradeable.sol +128 -0
- package/contracts/fee-config/FeeConfigRBACUpgradeable.sol +44 -0
- package/contracts/libs/EnumerableSetPagination.sol +77 -0
- package/contracts/pause/PauseBaseUpgradeable.sol +107 -0
- package/contracts/pause/PauseRBACUpgradeable.sol +47 -0
- package/contracts/pause-by-id/PauseByIDBaseUpgradeable.sol +127 -0
- package/contracts/pause-by-id/PauseByIDRBACUpgradeable.sol +74 -0
- package/contracts/rate-limiter/RateLimiterBaseUpgradeable.sol +636 -0
- package/contracts/rate-limiter/RateLimiterRBACUpgradeable.sol +89 -0
- package/contracts/rate-limiter/libs/RateLimiterUtils.sol +82 -0
- package/foundry.toml +27 -0
- package/package.json +34 -0
- package/solhint.config.js +3 -0
- package/test/AccessControl2StepUpgradeable.t.sol +374 -0
- package/test/AllowlistBaseUpgradeable.t.sol +380 -0
- package/test/AllowlistRBACUpgradeable.t.sol +84 -0
- package/test/EnumerableSetPagination.t.sol +445 -0
- package/test/FeeConfigBaseUpgradeable.t.sol +232 -0
- package/test/FeeConfigRBACUpgradeable.t.sol +59 -0
- package/test/FeeHandlerBaseUpgradeable.t.sol +78 -0
- package/test/FeeHandlerRBACUpgradeable.t.sol +45 -0
- package/test/PauseBaseUpgradeable.t.sol +244 -0
- package/test/PauseByIDBaseUpgradeable.t.sol +337 -0
- package/test/PauseByIDRBACUpgradeable.t.sol +414 -0
- package/test/PauseRBACUpgradeable.t.sol +258 -0
- package/test/RateLimiterBaseUpgradeable.t.sol +826 -0
- package/test/RateLimiterRBACUpgradeable.t.sol +128 -0
- package/test/RateLimiterRBACUpgradeableInvariant.t.sol +444 -0
- package/test/RateLimiterUtils.t.sol +137 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.22;
|
|
3
|
+
|
|
4
|
+
import { IRateLimiter } from "@layerzerolabs/utils-evm-contracts/contracts/interfaces/IRateLimiter.sol";
|
|
5
|
+
import { AccessControl2StepUpgradeable } from "./../access/AccessControl2StepUpgradeable.sol";
|
|
6
|
+
import { RateLimiterBaseUpgradeable } from "./RateLimiterBaseUpgradeable.sol";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @title RateLimiterRBACUpgradeable
|
|
10
|
+
* @author LayerZero Labs (tinom.eth)
|
|
11
|
+
* @custom:version 1.0.0
|
|
12
|
+
* @notice Abstract upgradeable contract that provides toggleable rate limiting functionality for OApps.
|
|
13
|
+
* Token bucket algorithm with linear decay.
|
|
14
|
+
* @dev Exposes public management functions through `AccessControl2StepUpgradeable`.
|
|
15
|
+
* @dev Configured limits must be significantly larger than windows to avoid precision loss when calculating decays.
|
|
16
|
+
* @dev Net rate limits offset outflow usage with inflows and vice versa, gross rate limits do not.
|
|
17
|
+
* @dev Amounts are stored as `uint96`, any amounts larger than `type(uint96).max` need to be downscaled via the
|
|
18
|
+
* `SCALE_DECIMALS` constructor parameter.
|
|
19
|
+
* @dev When using global state, ID-specific configs are ignored, and the default config is always used.
|
|
20
|
+
*/
|
|
21
|
+
abstract contract RateLimiterRBACUpgradeable is
|
|
22
|
+
IRateLimiter,
|
|
23
|
+
RateLimiterBaseUpgradeable,
|
|
24
|
+
AccessControl2StepUpgradeable
|
|
25
|
+
{
|
|
26
|
+
/// @notice Role for configuring rate limits.
|
|
27
|
+
bytes32 public constant RATE_LIMITER_MANAGER_ROLE = keccak256("RATE_LIMITER_MANAGER_ROLE");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @dev Sets immutable variables.
|
|
31
|
+
* @param _scaleDecimals Number of decimals to scale the rate limit amounts, usually 0
|
|
32
|
+
*/
|
|
33
|
+
constructor(uint8 _scaleDecimals) RateLimiterBaseUpgradeable(_scaleDecimals) {}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @notice Initializes the contract.
|
|
37
|
+
* @dev This function is empty on purpose, as no custom logic is needed.
|
|
38
|
+
*/
|
|
39
|
+
function __RateLimiterRBAC_init() internal onlyInitializing {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @notice Unchained initialization function for the contract.
|
|
43
|
+
* @dev This function is empty on purpose, as no custom logic is needed.
|
|
44
|
+
*/
|
|
45
|
+
function __RateLimiterRBAC_init_unchained() internal onlyInitializing {}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @inheritdoc IRateLimiter
|
|
49
|
+
*/
|
|
50
|
+
function setRateLimitGlobalConfig(
|
|
51
|
+
RateLimitGlobalConfig memory _globalConfig
|
|
52
|
+
) public virtual onlyRole(RATE_LIMITER_MANAGER_ROLE) {
|
|
53
|
+
_setRateLimitGlobalConfig(_globalConfig);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @inheritdoc IRateLimiter
|
|
58
|
+
*/
|
|
59
|
+
function setRateLimitConfigs(
|
|
60
|
+
SetRateLimitConfigParam[] calldata _params
|
|
61
|
+
) public virtual onlyRole(RATE_LIMITER_MANAGER_ROLE) {
|
|
62
|
+
_setRateLimitConfigs(_params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @inheritdoc IRateLimiter
|
|
67
|
+
*/
|
|
68
|
+
function setRateLimitStates(
|
|
69
|
+
SetRateLimitStateParam[] calldata _params
|
|
70
|
+
) public virtual onlyRole(RATE_LIMITER_MANAGER_ROLE) {
|
|
71
|
+
_setRateLimitStates(_params);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @inheritdoc IRateLimiter
|
|
76
|
+
*/
|
|
77
|
+
function setRateLimitAddressExemptions(
|
|
78
|
+
SetRateLimitAddressExemptionParam[] calldata _exemptions
|
|
79
|
+
) public virtual onlyRole(RATE_LIMITER_MANAGER_ROLE) {
|
|
80
|
+
_setRateLimitAddressExemptions(_exemptions);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @inheritdoc IRateLimiter
|
|
85
|
+
*/
|
|
86
|
+
function checkpointRateLimits(uint256[] calldata _ids) public virtual onlyRole(RATE_LIMITER_MANAGER_ROLE) {
|
|
87
|
+
_checkpointRateLimits(_ids);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.22;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title RateLimiterUtils
|
|
6
|
+
* @author LayerZero Labs (tinom.eth)
|
|
7
|
+
* @custom:version 1.0.0
|
|
8
|
+
* @notice Library for utility functions for the `RateLimiter` contract.
|
|
9
|
+
*/
|
|
10
|
+
library RateLimiterUtils {
|
|
11
|
+
uint24 private constant BIT_MASK = 1;
|
|
12
|
+
|
|
13
|
+
uint24 private constant OVERRIDE_DEFAULT_CONFIG_BIT = 0;
|
|
14
|
+
uint24 private constant OUTBOUND_ENABLED_BIT = 1;
|
|
15
|
+
uint24 private constant INBOUND_ENABLED_BIT = 2;
|
|
16
|
+
uint24 private constant NET_ACCOUNTING_BIT = 3;
|
|
17
|
+
uint24 private constant ADDRESS_EXEMPTION_BIT = 4;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @notice Decodes the override default configuration flag from the configuration bitmap.
|
|
21
|
+
* @param _config Bitmap of the rate limit configuration
|
|
22
|
+
* @return overrideDefaultConfig Whether to override the default configuration
|
|
23
|
+
*/
|
|
24
|
+
function decodeOverrideDefaultConfig(uint24 _config) internal pure returns (bool overrideDefaultConfig) {
|
|
25
|
+
assembly {
|
|
26
|
+
overrideDefaultConfig := and(shr(OVERRIDE_DEFAULT_CONFIG_BIT, _config), BIT_MASK)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @notice Decodes the configuration bitmap into its components, excluding the override default configuration flag.
|
|
32
|
+
* @param _config Bitmap of the rate limit configuration
|
|
33
|
+
* @return outboundEnabled Whether outbound is enabled
|
|
34
|
+
* @return inboundEnabled Whether inbound is enabled
|
|
35
|
+
* @return netAccountingEnabled Whether net accounting is enabled
|
|
36
|
+
* @return addressExemptionEnabled Whether address exemption is enabled
|
|
37
|
+
*/
|
|
38
|
+
function decodeConfigBitmapFlags(
|
|
39
|
+
uint24 _config
|
|
40
|
+
)
|
|
41
|
+
internal
|
|
42
|
+
pure
|
|
43
|
+
returns (bool outboundEnabled, bool inboundEnabled, bool netAccountingEnabled, bool addressExemptionEnabled)
|
|
44
|
+
{
|
|
45
|
+
assembly {
|
|
46
|
+
outboundEnabled := and(shr(OUTBOUND_ENABLED_BIT, _config), BIT_MASK)
|
|
47
|
+
inboundEnabled := and(shr(INBOUND_ENABLED_BIT, _config), BIT_MASK)
|
|
48
|
+
netAccountingEnabled := and(shr(NET_ACCOUNTING_BIT, _config), BIT_MASK)
|
|
49
|
+
addressExemptionEnabled := and(shr(ADDRESS_EXEMPTION_BIT, _config), BIT_MASK)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @notice Encodes the configuration bitmap into its components.
|
|
55
|
+
* @param _overrideDefaultConfig Whether to override the default configuration
|
|
56
|
+
* @param _outboundEnabled Whether outbound is enabled
|
|
57
|
+
* @param _inboundEnabled Whether inbound is enabled
|
|
58
|
+
* @param _netAccountingEnabled Whether net accounting is enabled
|
|
59
|
+
* @param _addressExemptionEnabled Whether address exemption is enabled
|
|
60
|
+
* @return config Configuration bitmap
|
|
61
|
+
*/
|
|
62
|
+
function encodeConfigBitmap(
|
|
63
|
+
bool _overrideDefaultConfig,
|
|
64
|
+
bool _outboundEnabled,
|
|
65
|
+
bool _inboundEnabled,
|
|
66
|
+
bool _netAccountingEnabled,
|
|
67
|
+
bool _addressExemptionEnabled
|
|
68
|
+
) internal pure returns (uint24 config) {
|
|
69
|
+
assembly {
|
|
70
|
+
config := or(
|
|
71
|
+
or(
|
|
72
|
+
or(
|
|
73
|
+
shl(OVERRIDE_DEFAULT_CONFIG_BIT, _overrideDefaultConfig),
|
|
74
|
+
shl(OUTBOUND_ENABLED_BIT, _outboundEnabled)
|
|
75
|
+
),
|
|
76
|
+
or(shl(INBOUND_ENABLED_BIT, _inboundEnabled), shl(NET_ACCOUNTING_BIT, _netAccountingEnabled))
|
|
77
|
+
),
|
|
78
|
+
shl(ADDRESS_EXEMPTION_BIT, _addressExemptionEnabled)
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/foundry.toml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[profile.default]
|
|
2
|
+
solc = '0.8.22'
|
|
3
|
+
verbosity = 3
|
|
4
|
+
src = "contracts"
|
|
5
|
+
test = "test"
|
|
6
|
+
out = "artifacts"
|
|
7
|
+
cache_path = "cache"
|
|
8
|
+
libs = ['node_modules', 'node_modules/@layerzerolabs/toolbox-foundry/lib']
|
|
9
|
+
optimizer = true
|
|
10
|
+
optimizer_runs = 200
|
|
11
|
+
|
|
12
|
+
remappings = [
|
|
13
|
+
'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test/',
|
|
14
|
+
'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std/',
|
|
15
|
+
'solidity-bytes-utils/contracts/=node_modules/@layerzerolabs/toolbox-foundry/lib/solidity-bytes-utils/',
|
|
16
|
+
'@openzeppelin/=node_modules/@openzeppelin/',
|
|
17
|
+
'@layerzerolabs/=node_modules/@layerzerolabs/',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[fuzz]
|
|
21
|
+
runs = 1000
|
|
22
|
+
|
|
23
|
+
[invariant]
|
|
24
|
+
runs = 256
|
|
25
|
+
depth = 15
|
|
26
|
+
fail_on_revert = false
|
|
27
|
+
shrink_run_limit = 2048
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@layerzerolabs/utils-upgradeable-evm-contracts",
|
|
3
|
+
"version": "0.2.74",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "LayerZero Labs reference EVM upgradeable utility implementations",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@layerzerolabs/test-devtools-evm-foundry": "8.0.1",
|
|
9
|
+
"@layerzerolabs/toolbox-foundry": "^0.1.13",
|
|
10
|
+
"@openzeppelin/contracts": "^5.0.2",
|
|
11
|
+
"@openzeppelin/contracts-upgradeable": "^5.0.2",
|
|
12
|
+
"solhint": "^6.0.1",
|
|
13
|
+
"@layerzerolabs/solhint-configuration": "0.2.74",
|
|
14
|
+
"@layerzerolabs/typescript-configuration": "0.2.74",
|
|
15
|
+
"@layerzerolabs/utils-evm-contracts": "0.2.74",
|
|
16
|
+
"@layerzerolabs/vm-tooling-evm": "0.2.74",
|
|
17
|
+
"@layerzerolabs/tsup-configuration": "0.2.74"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "restricted",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"externalRepoConfig": {
|
|
24
|
+
"targets": [
|
|
25
|
+
"audit-external",
|
|
26
|
+
"monorepo-external"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean-artifacts": "rm -rf artifacts* cache hh-cache*",
|
|
31
|
+
"lint": "solhint --config solhint.config.js 'contracts/**/*.sol' 'test/**/*.sol' --fix --noPrompt",
|
|
32
|
+
"test": "pnpm lz-tool forge test"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.22;
|
|
3
|
+
|
|
4
|
+
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
5
|
+
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
|
|
6
|
+
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
|
7
|
+
import { Test } from "forge-std/Test.sol";
|
|
8
|
+
import {
|
|
9
|
+
AccessControl2StepUpgradeable,
|
|
10
|
+
IAccessControl2Step
|
|
11
|
+
} from "./../contracts/access/AccessControl2StepUpgradeable.sol";
|
|
12
|
+
|
|
13
|
+
contract AccessControl2StepUpgradeableMock is AccessControl2StepUpgradeable {
|
|
14
|
+
bytes32 public constant TEST_ROLE = keccak256("TEST_ROLE");
|
|
15
|
+
bytes32 public constant OTHER_ROLE = keccak256("OTHER_ROLE");
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
_disableInitializers();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function initialize(address _initialAdmin) public initializer {
|
|
22
|
+
__AccessControl2Step_init(_initialAdmin);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function exposedSetRoleAdmin(bytes32 role, bytes32 adminRole) external {
|
|
26
|
+
_setRoleAdmin(role, adminRole);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
contract AccessControl2StepUpgradeableTest is Test {
|
|
31
|
+
AccessControl2StepUpgradeableMock mock;
|
|
32
|
+
|
|
33
|
+
address initialAdmin = address(this);
|
|
34
|
+
address alice = makeAddr("alice");
|
|
35
|
+
address bob = makeAddr("bob");
|
|
36
|
+
address charlie = makeAddr("charlie");
|
|
37
|
+
address proxyAdmin;
|
|
38
|
+
|
|
39
|
+
bytes32 defaultAdminRole;
|
|
40
|
+
bytes32 testRole;
|
|
41
|
+
|
|
42
|
+
function setUp() public {
|
|
43
|
+
AccessControl2StepUpgradeableMock impl = new AccessControl2StepUpgradeableMock();
|
|
44
|
+
|
|
45
|
+
uint256 currentNonce = vm.getNonce(address(this));
|
|
46
|
+
proxyAdmin = vm.computeCreateAddress(vm.computeCreateAddress(address(this), currentNonce), 1);
|
|
47
|
+
|
|
48
|
+
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
|
49
|
+
address(impl),
|
|
50
|
+
address(this),
|
|
51
|
+
abi.encodeWithSelector(AccessControl2StepUpgradeableMock.initialize.selector, address(this))
|
|
52
|
+
);
|
|
53
|
+
mock = AccessControl2StepUpgradeableMock(address(proxy));
|
|
54
|
+
defaultAdminRole = mock.DEFAULT_ADMIN_ROLE();
|
|
55
|
+
testRole = mock.TEST_ROLE();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============ Initialization ============
|
|
59
|
+
|
|
60
|
+
function test_initialize_Success_AdminHasRole() public view {
|
|
61
|
+
assertTrue(mock.hasRole(defaultAdminRole, initialAdmin));
|
|
62
|
+
assertEq(mock.getRoleMemberCount(defaultAdminRole), 1);
|
|
63
|
+
assertEq(mock.getRoleMember(defaultAdminRole, 0), initialAdmin);
|
|
64
|
+
assertEq(mock.defaultAdmin(), initialAdmin);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function test_initialize_Revert_AlreadyInitialized() public {
|
|
68
|
+
vm.expectRevert(Initializable.InvalidInitialization.selector);
|
|
69
|
+
mock.initialize(address(0x999));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function test_initialize_Revert_InvalidDefaultAdmin() public {
|
|
73
|
+
AccessControl2StepUpgradeableMock impl = new AccessControl2StepUpgradeableMock();
|
|
74
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.InvalidDefaultAdmin.selector, address(0)));
|
|
75
|
+
new TransparentUpgradeableProxy(
|
|
76
|
+
address(impl),
|
|
77
|
+
address(this),
|
|
78
|
+
abi.encodeWithSelector(AccessControl2StepUpgradeableMock.initialize.selector, address(0))
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============ defaultAdmin ============
|
|
83
|
+
|
|
84
|
+
function test_defaultAdmin_ReturnsInitialAdmin() public view {
|
|
85
|
+
assertEq(mock.defaultAdmin(), initialAdmin);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============ pendingDefaultAdmin ============
|
|
89
|
+
|
|
90
|
+
function test_pendingDefaultAdmin_InitiallyZero() public view {
|
|
91
|
+
assertEq(mock.pendingDefaultAdmin(), address(0));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============ beginDefaultAdminTransfer ============
|
|
95
|
+
|
|
96
|
+
function test_beginDefaultAdminTransfer_Success() public {
|
|
97
|
+
vm.expectEmit(true, true, true, true, address(mock));
|
|
98
|
+
emit IAccessControl2Step.DefaultAdminTransferStarted(alice);
|
|
99
|
+
|
|
100
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
101
|
+
|
|
102
|
+
assertEq(mock.pendingDefaultAdmin(), alice);
|
|
103
|
+
assertEq(mock.defaultAdmin(), initialAdmin);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function test_beginDefaultAdminTransfer_Success_OverwritesPending() public {
|
|
107
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
108
|
+
assertEq(mock.pendingDefaultAdmin(), alice);
|
|
109
|
+
|
|
110
|
+
mock.beginDefaultAdminTransfer(bob);
|
|
111
|
+
assertEq(mock.pendingDefaultAdmin(), bob);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function test_beginDefaultAdminTransfer_Success_CancelsBySettingZero() public {
|
|
115
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
116
|
+
assertEq(mock.pendingDefaultAdmin(), alice);
|
|
117
|
+
|
|
118
|
+
vm.expectEmit(true, true, true, true, address(mock));
|
|
119
|
+
emit IAccessControl2Step.DefaultAdminTransferStarted(address(0));
|
|
120
|
+
|
|
121
|
+
mock.beginDefaultAdminTransfer(address(0));
|
|
122
|
+
assertEq(mock.pendingDefaultAdmin(), address(0));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function test_beginDefaultAdminTransfer_Revert_Unauthorized() public {
|
|
126
|
+
vm.expectRevert(
|
|
127
|
+
abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, alice, defaultAdminRole)
|
|
128
|
+
);
|
|
129
|
+
vm.prank(alice);
|
|
130
|
+
mock.beginDefaultAdminTransfer(bob);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============ acceptDefaultAdminTransfer ============
|
|
134
|
+
|
|
135
|
+
function test_acceptDefaultAdminTransfer_Success() public {
|
|
136
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
137
|
+
|
|
138
|
+
vm.prank(alice);
|
|
139
|
+
mock.acceptDefaultAdminTransfer();
|
|
140
|
+
|
|
141
|
+
assertTrue(mock.hasRole(defaultAdminRole, alice));
|
|
142
|
+
assertFalse(mock.hasRole(defaultAdminRole, initialAdmin));
|
|
143
|
+
assertEq(mock.getRoleMemberCount(defaultAdminRole), 1);
|
|
144
|
+
assertEq(mock.getRoleMember(defaultAdminRole, 0), alice);
|
|
145
|
+
assertEq(mock.defaultAdmin(), alice);
|
|
146
|
+
assertEq(mock.pendingDefaultAdmin(), address(0));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function test_acceptDefaultAdminTransfer_Revert_NotPendingAdmin() public {
|
|
150
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
151
|
+
|
|
152
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.CallerNotPendingAdmin.selector, alice));
|
|
153
|
+
vm.prank(bob);
|
|
154
|
+
mock.acceptDefaultAdminTransfer();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function test_acceptDefaultAdminTransfer_Revert_NoPendingTransfer() public {
|
|
158
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.CallerNotPendingAdmin.selector, address(0)));
|
|
159
|
+
vm.prank(alice);
|
|
160
|
+
mock.acceptDefaultAdminTransfer();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============ grantRole ============
|
|
164
|
+
|
|
165
|
+
function test_grantRole_Revert_DefaultAdminRole() public {
|
|
166
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.AccessControlEnforcedDefaultAdminRules.selector));
|
|
167
|
+
mock.grantRole(defaultAdminRole, alice);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function test_grantRole_Success_NonAdminRole() public {
|
|
171
|
+
mock.grantRole(testRole, alice);
|
|
172
|
+
assertTrue(mock.hasRole(testRole, alice));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============ revokeRole ============
|
|
176
|
+
|
|
177
|
+
function test_revokeRole_Revert_DefaultAdminRole_NonAdmin() public {
|
|
178
|
+
vm.expectRevert(
|
|
179
|
+
abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, alice, defaultAdminRole)
|
|
180
|
+
);
|
|
181
|
+
vm.prank(alice);
|
|
182
|
+
mock.revokeRole(defaultAdminRole, initialAdmin);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function test_revokeRole_Revert_DefaultAdminRole_Admin() public {
|
|
186
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.AccessControlEnforcedDefaultAdminRules.selector));
|
|
187
|
+
mock.revokeRole(defaultAdminRole, initialAdmin);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function test_revokeRole_Success_NonAdminRole_Admin() public {
|
|
191
|
+
mock.grantRole(testRole, alice);
|
|
192
|
+
assertTrue(mock.hasRole(testRole, alice));
|
|
193
|
+
|
|
194
|
+
mock.revokeRole(testRole, alice);
|
|
195
|
+
assertFalse(mock.hasRole(testRole, alice));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function test_revokeRole_Revert_NonAdminRole_NonAdmin() public {
|
|
199
|
+
mock.grantRole(testRole, alice);
|
|
200
|
+
assertTrue(mock.hasRole(testRole, alice));
|
|
201
|
+
|
|
202
|
+
vm.expectRevert(
|
|
203
|
+
abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, alice, defaultAdminRole)
|
|
204
|
+
);
|
|
205
|
+
vm.prank(alice);
|
|
206
|
+
mock.revokeRole(testRole, alice);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============ renounceRole ============
|
|
210
|
+
|
|
211
|
+
function test_renounceRole_Revert_DefaultAdminRole() public {
|
|
212
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.AccessControlEnforcedDefaultAdminRules.selector));
|
|
213
|
+
mock.renounceRole(defaultAdminRole, initialAdmin);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function test_renounceRole_Revert_DefaultAdminRoleDuringPendingTransfer() public {
|
|
217
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
218
|
+
|
|
219
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.AccessControlEnforcedDefaultAdminRules.selector));
|
|
220
|
+
mock.renounceRole(defaultAdminRole, initialAdmin);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function test_renounceRole_Success_NonAdminRole_NonAdmin() public {
|
|
224
|
+
mock.grantRole(testRole, alice);
|
|
225
|
+
|
|
226
|
+
vm.startPrank(alice);
|
|
227
|
+
mock.renounceRole(testRole, alice);
|
|
228
|
+
vm.stopPrank();
|
|
229
|
+
|
|
230
|
+
assertFalse(mock.hasRole(testRole, alice));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function test_renounceRole_Success_NonAdminRole_Admin() public {
|
|
234
|
+
mock.grantRole(testRole, initialAdmin);
|
|
235
|
+
mock.renounceRole(testRole, initialAdmin);
|
|
236
|
+
|
|
237
|
+
assertFalse(mock.hasRole(testRole, initialAdmin));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ============ setRoleAdmin ============
|
|
241
|
+
|
|
242
|
+
function test_setRoleAdmin_Success_NonAdminRole_Self() public {
|
|
243
|
+
mock.exposedSetRoleAdmin(testRole, defaultAdminRole);
|
|
244
|
+
assertEq(mock.getRoleAdmin(testRole), defaultAdminRole);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function test_setRoleAdmin_Success_NonAdminRole_Other() public {
|
|
248
|
+
bytes32 otherRole = mock.OTHER_ROLE();
|
|
249
|
+
mock.exposedSetRoleAdmin(testRole, otherRole);
|
|
250
|
+
assertEq(mock.getRoleAdmin(testRole), otherRole);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function test_setRoleAdmin_Revert_DefaultAdminRole() public {
|
|
254
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.AccessControlEnforcedDefaultAdminRules.selector));
|
|
255
|
+
mock.exposedSetRoleAdmin(defaultAdminRole, testRole);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ============ Storage ============
|
|
259
|
+
|
|
260
|
+
function test_storageHash() public pure {
|
|
261
|
+
assertEq(
|
|
262
|
+
keccak256(abi.encode(uint256(keccak256("layerzerov2.storage.accesscontrol2step")) - 1)) &
|
|
263
|
+
~bytes32(uint256(0xff)),
|
|
264
|
+
0x9ab055b8fb8e38b861ceee93a65f20f7a2382a86570a6abe934fe7edfac60400
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ============ Fuzz ============
|
|
269
|
+
|
|
270
|
+
function test_beginDefaultAdminTransfer_Fuzz(address _newAdmin) public {
|
|
271
|
+
mock.beginDefaultAdminTransfer(_newAdmin);
|
|
272
|
+
assertEq(mock.pendingDefaultAdmin(), _newAdmin);
|
|
273
|
+
assertEq(mock.defaultAdmin(), initialAdmin);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function test_acceptDefaultAdminTransfer_Fuzz(address _newAdmin) public {
|
|
277
|
+
vm.assume(_newAdmin != address(0));
|
|
278
|
+
vm.assume(_newAdmin != proxyAdmin);
|
|
279
|
+
vm.assume(_newAdmin != initialAdmin);
|
|
280
|
+
|
|
281
|
+
mock.beginDefaultAdminTransfer(_newAdmin);
|
|
282
|
+
vm.prank(_newAdmin);
|
|
283
|
+
mock.acceptDefaultAdminTransfer();
|
|
284
|
+
|
|
285
|
+
assertTrue(mock.hasRole(defaultAdminRole, _newAdmin));
|
|
286
|
+
assertFalse(mock.hasRole(defaultAdminRole, initialAdmin));
|
|
287
|
+
assertEq(mock.defaultAdmin(), _newAdmin);
|
|
288
|
+
assertEq(mock.pendingDefaultAdmin(), address(0));
|
|
289
|
+
assertEq(mock.getRoleMemberCount(defaultAdminRole), 1);
|
|
290
|
+
assertEq(mock.getRoleMember(defaultAdminRole, 0), _newAdmin);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ============ Edge Cases ============
|
|
294
|
+
|
|
295
|
+
function test_acceptDefaultAdminTransfer_Revert_StalePendingAfterOverwrite() public {
|
|
296
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
297
|
+
mock.beginDefaultAdminTransfer(bob); // Overwrite
|
|
298
|
+
|
|
299
|
+
// `alice` (stale pending) cannot accept.
|
|
300
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.CallerNotPendingAdmin.selector, bob));
|
|
301
|
+
vm.prank(alice);
|
|
302
|
+
mock.acceptDefaultAdminTransfer();
|
|
303
|
+
|
|
304
|
+
// `bob` (current pending) can accept.
|
|
305
|
+
vm.prank(bob);
|
|
306
|
+
mock.acceptDefaultAdminTransfer();
|
|
307
|
+
|
|
308
|
+
assertTrue(mock.hasRole(defaultAdminRole, bob));
|
|
309
|
+
assertFalse(mock.hasRole(defaultAdminRole, alice));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function test_acceptDefaultAdminTransfer_Revert_AfterCancel() public {
|
|
313
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
314
|
+
mock.beginDefaultAdminTransfer(address(0)); // Cancel
|
|
315
|
+
|
|
316
|
+
vm.expectRevert(abi.encodeWithSelector(IAccessControl2Step.CallerNotPendingAdmin.selector, address(0)));
|
|
317
|
+
vm.prank(alice);
|
|
318
|
+
mock.acceptDefaultAdminTransfer();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function test_acceptDefaultAdminTransfer_Events() public {
|
|
322
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
323
|
+
|
|
324
|
+
vm.expectEmit(true, true, true, true, address(mock));
|
|
325
|
+
emit IAccessControl.RoleRevoked(defaultAdminRole, initialAdmin, alice);
|
|
326
|
+
vm.expectEmit(true, true, true, true, address(mock));
|
|
327
|
+
emit IAccessControl.RoleGranted(defaultAdminRole, alice, alice);
|
|
328
|
+
|
|
329
|
+
vm.prank(alice);
|
|
330
|
+
mock.acceptDefaultAdminTransfer();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ============ Integration ============
|
|
334
|
+
|
|
335
|
+
function test_integration_TransferAndTransferAgain() public {
|
|
336
|
+
// Admin transfers to `alice`.
|
|
337
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
338
|
+
vm.prank(alice);
|
|
339
|
+
mock.acceptDefaultAdminTransfer();
|
|
340
|
+
|
|
341
|
+
// `alice` transfers to `bob`.
|
|
342
|
+
vm.prank(alice);
|
|
343
|
+
mock.beginDefaultAdminTransfer(bob);
|
|
344
|
+
vm.prank(bob);
|
|
345
|
+
mock.acceptDefaultAdminTransfer();
|
|
346
|
+
|
|
347
|
+
// `bob` transfers to `charlie`.
|
|
348
|
+
vm.prank(bob);
|
|
349
|
+
mock.beginDefaultAdminTransfer(charlie);
|
|
350
|
+
vm.prank(charlie);
|
|
351
|
+
mock.acceptDefaultAdminTransfer();
|
|
352
|
+
|
|
353
|
+
assertTrue(mock.hasRole(defaultAdminRole, charlie));
|
|
354
|
+
assertFalse(mock.hasRole(defaultAdminRole, bob));
|
|
355
|
+
assertFalse(mock.hasRole(defaultAdminRole, alice));
|
|
356
|
+
assertFalse(mock.hasRole(defaultAdminRole, initialAdmin));
|
|
357
|
+
assertEq(mock.getRoleMemberCount(defaultAdminRole), 1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function test_integration_TransferCancelRetransfer() public {
|
|
361
|
+
// Admin starts transfer to `alice` then cancels.
|
|
362
|
+
mock.beginDefaultAdminTransfer(alice);
|
|
363
|
+
mock.beginDefaultAdminTransfer(address(0)); // Cancel
|
|
364
|
+
|
|
365
|
+
// Admin starts transfer to `bob` and `bob` accepts.
|
|
366
|
+
mock.beginDefaultAdminTransfer(bob);
|
|
367
|
+
vm.prank(bob);
|
|
368
|
+
mock.acceptDefaultAdminTransfer();
|
|
369
|
+
|
|
370
|
+
assertTrue(mock.hasRole(defaultAdminRole, bob));
|
|
371
|
+
assertFalse(mock.hasRole(defaultAdminRole, initialAdmin));
|
|
372
|
+
assertEq(mock.getRoleMemberCount(defaultAdminRole), 1);
|
|
373
|
+
}
|
|
374
|
+
}
|