Skip to content

Commit b173eed

Browse files
committed
Merge branch 'crosschain-audit-oct-2024' into cross-chain/prototype-v1
2 parents 5fbdc75 + fef72ea commit b173eed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+3789
-89
lines changed

.github/actions/setup/action.yml

+3
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ runs:
1919
uses: foundry-rs/foundry-toolchain@v1
2020
with:
2121
version: nightly
22+
- name: Install submodule dependencies
23+
shell: bash
24+
run: forge install

.github/workflows/checks.yml

-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ jobs:
7070
submodules: recursive
7171
- name: Set up environment
7272
uses: ./.github/actions/setup
73-
- run: rm foundry.toml
7473
- uses: crytic/[email protected]
7574

7675
codespell:

CHANGELOG.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## 06-11-2024
2+
3+
* `ERC7739Utils`: Add a library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on the ERC-7739.
4+
* `ERC7739Signer`: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`.
5+
6+
## 15-10-2024
7+
8+
* `ERC20Collateral`: Extension of ERC-20 that limits the supply of tokens based on a collateral and time-based expiration.
9+
10+
## 10-10-2024
11+
12+
* `ERC20Allowlist`: Extension of ERC-20 that implements an allow list to enable token transfers, disabled by default.
13+
* `ERC20Blocklist`: Extension of ERC-20 that implements a block list to restrict token transfers, enabled by default.
14+
* `ERC20Custodian`: Extension of ERC-20 that allows a custodian to freeze user's tokens by a certain amount.
15+
16+
## 03-10-2024
17+
18+
* `OnTokenTransferAdapter`: An adapter that exposes `transferAndCall` on top of an ERC-1363 receiver.
19+
20+
## 15-05-2024
21+
22+
* `HybridProxy`: Add a proxy contract that can either use a beacon to retrieve the implementation or fallback to an address in the ERC-1967's implementation slot.
23+
24+
## 11-05-2024
25+
26+
* `AccessManagerLight`: Add a simpler version of the `AccessManager` in OpenZeppelin Contracts.
27+
* `ERC4626Fees`: Extension of ERC-4626 that implements fees on entry and exit from the vault.
28+
* `Masks`: Add library to handle `bytes32` masks.
29+

contracts/access/README.adoc

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
= Utilities
2+
3+
[.readme-notice]
4+
NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/proxy
5+
6+
This directory contains utility contracts to restrict access control in smart contracts. These include:
7+
8+
* {AccessManagerLight}: A simpler version of an AccessManager that uses `bytes8` roles to allow function calls identified by their 4-bytes selector.
9+
10+
== AccessManager
11+
12+
{{AccessManagerLight}}
13+
14+
15+

contracts/access/manager/AccessManagerLight.sol

+42-21
Original file line numberDiff line numberDiff line change
@@ -5,84 +5,104 @@ pragma solidity ^0.8.20;
55
import {IAuthority} from "@openzeppelin/contracts/access/manager/IAuthority.sol";
66
import {Masks} from "../../utils/Masks.sol";
77

8+
/**
9+
* @dev Light version of an AccessManager contract that defines `bytes8` roles
10+
* that are stored as requirements (see {getRequirements}) for each function.
11+
*
12+
* Each requirement is a bitmask of roles that are allowed to call a function
13+
* identified by its `bytes4` selector. Users have their permissioned stored
14+
* as a bitmask of roles they belong to.
15+
*
16+
* The admin role is a special role that has access to all functions and can
17+
* manage the roles of other users.
18+
*/
819
contract AccessManagerLight is IAuthority {
920
using Masks for *;
1021

11-
uint8 public constant ADMIN = 0x00;
12-
uint8 public constant PUBLIC = 0xFF;
13-
Masks.Mask public immutable ADMIN_MASK = ADMIN.toMask();
14-
Masks.Mask public immutable PUBLIC_MASK = PUBLIC.toMask();
22+
uint8 public constant ADMIN_ROLE = 0x00;
23+
uint8 public constant PUBLIC_ROLE = 0xFF;
24+
Masks.Mask public immutable ADMIN_MASK = ADMIN_ROLE.toMask();
25+
Masks.Mask public immutable PUBLIC_MASK = PUBLIC_ROLE.toMask();
1526

16-
mapping(address => Masks.Mask) private _permissions;
17-
mapping(address => mapping(bytes4 => Masks.Mask)) private _restrictions;
27+
mapping(address => Masks.Mask) private _groups;
28+
mapping(address => mapping(bytes4 => Masks.Mask)) private _requirements;
1829
mapping(uint8 => Masks.Mask) private _admin;
1930

2031
event GroupAdded(address indexed user, uint8 indexed group);
2132
event GroupRemoved(address indexed user, uint8 indexed group);
2233
event GroupAdmins(uint8 indexed group, Masks.Mask admins);
23-
event Requirements(address indexed target, bytes4 indexed selector, Masks.Mask groups);
34+
event RequirementsSet(address indexed target, bytes4 indexed selector, Masks.Mask groups);
2435

25-
error MissingPermissions(address user, Masks.Mask permissions, Masks.Mask restriction);
36+
error MissingPermissions(address user, Masks.Mask permissions, Masks.Mask requirement);
2637

27-
modifier onlyRole(Masks.Mask restriction) {
38+
/// @dev Throws if the specified requirement is not met by the caller's permissions (see {getGroups}).
39+
modifier onlyRole(Masks.Mask requirement) {
2840
Masks.Mask permissions = getGroups(msg.sender);
29-
if (permissions.intersection(restriction).isEmpty()) {
30-
revert MissingPermissions(msg.sender, permissions, restriction);
41+
if (permissions.intersection(requirement).isEmpty()) {
42+
revert MissingPermissions(msg.sender, permissions, requirement);
3143
}
3244
_;
3345
}
3446

47+
/// @dev Initializes the contract with the `admin` as the first member of the admin group.
3548
constructor(address admin) {
3649
_addGroup(admin, 0);
3750
}
3851

39-
// Getters
52+
/// @dev Returns whether the `caller` has the required permissions to call the `target` with the `selector`.
4053
function canCall(address caller, address target, bytes4 selector) public view returns (bool) {
4154
return !getGroups(caller).intersection(getRequirements(target, selector)).isEmpty();
4255
}
4356

57+
/// @dev Returns the groups that the `user` belongs to.
4458
function getGroups(address user) public view returns (Masks.Mask) {
45-
return _permissions[user].union(PUBLIC_MASK);
59+
return _groups[user].union(PUBLIC_MASK);
4660
}
4761

62+
/// @dev Returns the admins of the `group`.
4863
function getGroupAdmins(uint8 group) public view returns (Masks.Mask) {
4964
return _admin[group].union(ADMIN_MASK); // Admin have power over all groups
5065
}
5166

67+
/// @dev Returns the requirements for the `target` and `selector`.
5268
function getRequirements(address target, bytes4 selector) public view returns (Masks.Mask) {
53-
return _restrictions[target][selector].union(ADMIN_MASK); // Admins can call an function
69+
return _requirements[target][selector].union(ADMIN_MASK); // Admins can call an function
5470
}
5571

56-
// Group management
72+
/// @dev Adds the `user` to the `group`. Emits {GroupAdded} event.
5773
function addGroup(address user, uint8 group) public onlyRole(getGroupAdmins(group)) {
5874
_addGroup(user, group);
5975
}
6076

77+
/// @dev Removes the `user` from the `group`. Emits {GroupRemoved} event.
6178
function remGroup(address user, uint8 group) public onlyRole(getGroupAdmins(group)) {
6279
_remGroup(user, group);
6380
}
6481

82+
/// @dev Internal version of {addGroup} without access control.
6583
function _addGroup(address user, uint8 group) internal {
66-
_permissions[user] = _permissions[user].union(group.toMask());
84+
_groups[user] = _groups[user].union(group.toMask());
6785
emit GroupAdded(user, group);
6886
}
6987

88+
/// @dev Internal version of {remGroup} without access control.
7089
function _remGroup(address user, uint8 group) internal {
71-
_permissions[user] = _permissions[user].difference(group.toMask());
90+
_groups[user] = _groups[user].difference(group.toMask());
7291
emit GroupRemoved(user, group);
7392
}
7493

75-
// Group admin management
94+
/// @dev Sets the `admins` of the `group`. Emits {GroupAdmins} event.
7695
function setGroupAdmins(uint8 group, uint8[] calldata admins) public onlyRole(ADMIN_MASK) {
7796
_setGroupAdmins(group, admins.toMask());
7897
}
7998

99+
/// @dev Internal version of {_setGroupAdmins} without access control.
80100
function _setGroupAdmins(uint8 group, Masks.Mask admins) internal {
81101
_admin[group] = admins;
82102
emit GroupAdmins(group, admins);
83103
}
84104

85-
// Requirement management
105+
/// @dev Sets the `groups` requirements for the `selectors` of the `target`.
86106
function setRequirements(
87107
address target,
88108
bytes4[] calldata selectors,
@@ -94,8 +114,9 @@ contract AccessManagerLight is IAuthority {
94114
}
95115
}
96116

117+
/// @dev Internal version of {_setRequirements} without access control.
97118
function _setRequirements(address target, bytes4 selector, Masks.Mask groups) internal {
98-
_restrictions[target][selector] = groups;
99-
emit Requirements(target, selector, groups);
119+
_requirements[target][selector] = groups;
120+
emit RequirementsSet(target, selector, groups);
100121
}
101122
}

contracts/mocks/ERC7739SignerMock.sol

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
6+
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
7+
import {ERC7739Signer} from "../utils/cryptography/draft-ERC7739Signer.sol";
8+
9+
contract ERC7739SignerMock is ERC7739Signer {
10+
address private immutable _eoa;
11+
12+
constructor(address eoa) EIP712("ERC7739SignerMock", "1") {
13+
_eoa = eoa;
14+
}
15+
16+
function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) {
17+
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
18+
return _eoa == recovered && err == ECDSA.RecoverError.NoError;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// contracts/MyStablecoinAllowlist.sol
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity ^0.8.22;
4+
5+
import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol";
6+
import {ERC20Allowlist, ERC20} from "../../token/ERC20/extensions/ERC20Allowlist.sol";
7+
8+
contract MyStablecoinAllowlist is ERC20Allowlist, AccessManaged {
9+
constructor(address initialAuthority) ERC20("MyStablecoin", "MST") AccessManaged(initialAuthority) {}
10+
11+
function allowUser(address user) public restricted {
12+
_allowUser(user);
13+
}
14+
15+
function disallowUser(address user) public restricted {
16+
_disallowUser(user);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {ERC20, ERC20Collateral} from "../../token/ERC20/extensions/ERC20Collateral.sol";
6+
7+
abstract contract ERC20CollateralMock is ERC20Collateral {
8+
uint48 private _timestamp;
9+
constructor(
10+
uint48 liveness_,
11+
string memory name_,
12+
string memory symbol_
13+
) ERC20(name_, symbol_) ERC20Collateral(liveness_) {
14+
_timestamp = clock();
15+
}
16+
17+
function collateral() public view override returns (uint256 amount, uint48 timestamp) {
18+
return (type(uint128).max, _timestamp);
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {ERC20, ERC20Custodian} from "../../token/ERC20/extensions/ERC20Custodian.sol";
6+
7+
abstract contract ERC20CustodianMock is ERC20Custodian {
8+
address private immutable _custodian;
9+
10+
constructor(address custodian, string memory name_, string memory symbol_) ERC20(name_, symbol_) {
11+
_custodian = custodian;
12+
}
13+
14+
function _isCustodian(address user) internal view override returns (bool) {
15+
return user == _custodian;
16+
}
17+
}

contracts/proxy/HybridProxy.sol

+21
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,35 @@ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.s
77
import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";
88
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
99

10+
/**
11+
* @dev A version of an ERC-1967 proxy that uses the address stored in the implementation slot as a beacon.
12+
*
13+
* The design allows to set an initial beacon that the contract may quit by upgrading to its own implementation
14+
* afterwards.
15+
*
16+
* WARNING: The fallback mechanism relies on the implementation not to define the {IBeacon-implementation} function.
17+
* Consider that if your implementation has this function, it'll be assumed as the beacon address, meaning that
18+
* the returned address will be used as this proxy's implementation.
19+
*/
1020
contract HybridProxy is Proxy {
21+
/**
22+
* @dev Initializes the proxy with an initial implementation. If data is present, it will be used to initialize the
23+
* implementation using a delegate call.
24+
*/
1125
constructor(address implementation, bytes memory data) {
1226
ERC1967Utils.upgradeToAndCall(implementation, "");
1327
if (data.length > 0) {
1428
Address.functionDelegateCall(_implementation(), data);
1529
}
1630
}
1731

32+
/**
33+
* @dev Returns the current implementation address according to ERC-1967's implementation slot.
34+
*
35+
* IMPORTANT: The way this function identifies whether the implementation is a beacon, is by checking
36+
* if it implements the {IBeacon-implementation} function. Consider that an actual implementation could
37+
* define this function, mistakenly identifying it as a beacon.
38+
*/
1839
function _implementation() internal view override returns (address) {
1940
address implementation = ERC1967Utils.getImplementation();
2041
try IBeacon(implementation).implementation() returns (address result) {

contracts/proxy/README.adoc

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
= Utilities
2+
3+
[.readme-notice]
4+
NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/proxy
5+
6+
Variants of proxy patterns, which are contracts that allow to forward a call to an implementation contract by using `delegatecall`. This contracts include:
7+
8+
* {HybridProxy}: An ERC-1967 proxy that uses the implementation slot as a beacon in a way that a user can upgrade to an implementation of their choice.
9+
10+
== General
11+
12+
{{HybridProxy}}
13+
14+
15+

0 commit comments

Comments
 (0)