Skip to content

Commit a86f472

Browse files
Merge pull request #233 from gildlab/2025-04-21-authorizor-event
2025 04 21 authorizor event
2 parents fa21657 + 889470a commit a86f472

File tree

8 files changed

+320
-166
lines changed

8 files changed

+320
-166
lines changed

.gas-snapshot

Lines changed: 157 additions & 157 deletions
Large diffs are not rendered by default.

src/abstract/ReceiptVault.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
WrongManager
3131
} from "../error/ErrReceiptVault.sol";
3232
import {UnmanagedReceiptTransfer} from "../interface/IReceiptManagerV2.sol";
33+
import {ERC165Upgradeable as ERC165} from
34+
"openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol";
3335

3436
/// Represents the action being taken on shares, ostensibly for calculating a
3537
/// ratio.
@@ -111,7 +113,8 @@ abstract contract ReceiptVault is
111113
ReentrancyGuard,
112114
ERC20,
113115
IReceiptVaultV3,
114-
ICloneableV2
116+
ICloneableV2,
117+
ERC165
115118
{
116119
using LibFixedPointDecimalArithmeticOpenZeppelin for uint256;
117120
using SafeERC20 for IERC20;
@@ -166,6 +169,12 @@ abstract contract ReceiptVault is
166169
}
167170
}
168171

172+
/// @inheritdoc ERC165
173+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
174+
return interfaceId == type(IReceiptManagerV2).interfaceId || interfaceId == type(ICloneableV2).interfaceId
175+
|| interfaceId == type(IReceiptVaultV3).interfaceId || super.supportsInterface(interfaceId);
176+
}
177+
169178
/// @inheritdoc IReceiptVaultV1
170179
function asset() public view virtual returns (address) {
171180
return address(sAsset);

src/concrete/vault/OffchainAssetReceiptVault.sol

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
279279
address sender, address confiscatee, uint256 id, uint256 targetAmount, uint256 confiscated, bytes justification
280280
);
281281

282-
IAuthorizeV1 sAuthorizer;
282+
/// The authorizer contract that is used to authorize actions in the vault.
283+
IAuthorizeV1 internal sAuthorizer;
283284

284285
/// The largest issued id. The next id issued will be larger than this.
285286
uint256 internal sHighwaterId;
@@ -310,7 +311,7 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
310311
revert ZeroInitialAdmin();
311312
}
312313

313-
sAuthorizer = IAuthorizeV1(address(this));
314+
_setAuthorizer(IAuthorizeV1(address(this)));
314315

315316
_transferOwnership(config.initialAdmin);
316317

@@ -330,6 +331,11 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
330331
return sHighwaterId;
331332
}
332333

334+
/// @inheritdoc IERC165
335+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
336+
return interfaceId == type(IAuthorizeV1).interfaceId || super.supportsInterface(interfaceId);
337+
}
338+
333339
/// Returns the current authorizer contract.
334340
function authorizer() external view returns (IAuthorizeV1) {
335341
return sAuthorizer;
@@ -344,14 +350,23 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
344350
revert Unauthorized(user, permission, data);
345351
}
346352

347-
/// Sets the authorizer contract. This is a critical operation and should be
348-
/// done with extreme care by the owner.
353+
/// Internal function to set the authorizer contract. This has no access
354+
/// control so it MUST only be externally accessible by functions with an
355+
/// access check.
349356
/// @param newAuthorizer The new authorizer contract.
350-
function setAuthorizer(IAuthorizeV1 newAuthorizer) external onlyOwner {
357+
function _setAuthorizer(IAuthorizeV1 newAuthorizer) internal {
351358
if (!IERC165(address(newAuthorizer)).supportsInterface(type(IAuthorizeV1).interfaceId)) {
352359
revert IncompatibleAuthorizer();
353360
}
354361
sAuthorizer = newAuthorizer;
362+
emit AuthorizerSet(msg.sender, newAuthorizer);
363+
}
364+
365+
/// Sets the authorizer contract. This is a critical operation and should be
366+
/// done with extreme care by the owner.
367+
/// @param newAuthorizer The new authorizer contract.
368+
function setAuthorizer(IAuthorizeV1 newAuthorizer) external onlyOwner {
369+
_setAuthorizer(newAuthorizer);
355370
}
356371

357372
/// Apply standard transfer restrictions to receipt transfers.

src/interface/IAuthorizeV1.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ error Unauthorized(address user, bytes32 permission, bytes data);
3333
/// sensitive operation and should be done with care, such as through a multisig
3434
/// and/or dedicated governance contract.
3535
interface IAuthorizeV1 {
36+
/// MUST be emitted when a new authorizer contract is set.
37+
/// This includes the initial setting of the authorizer contract if it is
38+
/// set in the constructor/initializer.
39+
/// @param sender The msg sender setting the authorizer.
40+
/// @param authorizer The new authorizer contract.
41+
event AuthorizerSet(address sender, IAuthorizeV1 authorizer);
42+
3643
/// Authorize a user for a caller-specified permission.
3744
///
3845
/// The authorization contract is expected to be implemented to be compatible
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {
6+
ReceiptVault,
7+
ReceiptVaultConstructionConfigV2,
8+
IReceiptV3,
9+
ICloneableFactoryV2,
10+
IReceiptManagerV2,
11+
IReceiptVaultV3,
12+
ICloneableV2
13+
} from "src/abstract/ReceiptVault.sol";
14+
import {Test} from "forge-std/Test.sol";
15+
16+
import {IERC165} from "openzeppelin-contracts/contracts/utils/introspection/IERC165.sol";
17+
18+
contract ConcreteReceiptVault is ReceiptVault {
19+
constructor()
20+
ReceiptVault(
21+
ReceiptVaultConstructionConfigV2({
22+
factory: ICloneableFactoryV2(address(0)),
23+
receiptImplementation: IReceiptV3(address(0))
24+
})
25+
)
26+
{}
27+
28+
function initialize(bytes calldata) external pure returns (bytes32) {
29+
// Fails initialize but we never call it.
30+
return bytes32(0);
31+
}
32+
}
33+
34+
contract ReceiptVaultIERC165Test is Test {
35+
function testReceiptVaultIERC165(bytes4 badInterfaceId) external {
36+
vm.assume(badInterfaceId != type(IERC165).interfaceId);
37+
vm.assume(badInterfaceId != type(IReceiptManagerV2).interfaceId);
38+
vm.assume(badInterfaceId != type(IReceiptVaultV3).interfaceId);
39+
vm.assume(badInterfaceId != type(ICloneableV2).interfaceId);
40+
41+
ConcreteReceiptVault receiptVault = new ConcreteReceiptVault();
42+
assertTrue(receiptVault.supportsInterface(type(IERC165).interfaceId));
43+
assertTrue(receiptVault.supportsInterface(type(IReceiptManagerV2).interfaceId));
44+
assertTrue(receiptVault.supportsInterface(type(IReceiptVaultV3).interfaceId));
45+
assertTrue(receiptVault.supportsInterface(type(ICloneableV2).interfaceId));
46+
47+
assertFalse(receiptVault.supportsInterface(badInterfaceId));
48+
}
49+
}

test/src/concrete/vault/OffchainAssetReceiptVault.authorize.t.sol

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ pragma solidity =0.8.25;
55
import {OffchainAssetReceiptVaultTest, Vm} from "test/abstract/OffchainAssetReceiptVaultTest.sol";
66
import {LibOffchainAssetVaultCreator} from "test/lib/LibOffchainAssetVaultCreator.sol";
77
import {LibUniqueAddressesGenerator} from "../../../lib/LibUniqueAddressesGenerator.sol";
8-
import {OffchainAssetReceiptVault, IAuthorizeV1} from "src/concrete/vault/OffchainAssetReceiptVault.sol";
8+
import {
9+
OffchainAssetReceiptVault,
10+
IAuthorizeV1,
11+
Unauthorized,
12+
CERTIFY,
13+
CertifyStateChange
14+
} from "src/concrete/vault/OffchainAssetReceiptVault.sol";
915
import {LibExtrospectERC1167Proxy} from "rain.extrospection/lib/LibExtrospectERC1167Proxy.sol";
1016
import {IERC165Upgradeable as IERC165} from
1117
"openzeppelin-contracts-upgradeable/contracts/utils/introspection/IERC165Upgradeable.sol";
@@ -37,6 +43,18 @@ contract OffchainAssetReceiptVaultAuthorizeTest is OffchainAssetReceiptVaultTest
3743
(bool isProxy, address implementation) = LibExtrospectERC1167Proxy.isERC1167Proxy(authorizer.code);
3844
assertTrue(isProxy);
3945
assertEq(implementation, address(iAuthorizerImplementation));
46+
CertifyStateChange memory certifyStateChange = CertifyStateChange({
47+
oldCertifiedUntil: 0,
48+
newCertifiedUntil: 1234,
49+
userCertifyUntil: 1234,
50+
forceUntil: true,
51+
data: ""
52+
});
53+
// Smoke test the authorizer NOT authorizing.
54+
vm.expectRevert(
55+
abi.encodeWithSelector(Unauthorized.selector, address(this), CERTIFY, abi.encode(certifyStateChange))
56+
);
57+
vault.certify(certifyStateChange.newCertifiedUntil, certifyStateChange.forceUntil, "");
4058
}
4159

4260
/// Test that the owner can change the authorizer.
@@ -58,6 +76,8 @@ contract OffchainAssetReceiptVaultAuthorizeTest is OffchainAssetReceiptVaultTest
5876
AlwaysAuthorize alwaysAuthorize = new AlwaysAuthorize();
5977

6078
vm.prank(alice);
79+
vm.expectEmit(false, false, false, true);
80+
emit IAuthorizeV1.AuthorizerSet(address(alice), alwaysAuthorize);
6181
vault.setAuthorizer(alwaysAuthorize);
6282

6383
authorizer = address(vault.authorizer());

test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ contract OffChainAssetReceiptVaultTest is OffchainAssetReceiptVaultTest {
6868
address msgSender;
6969

7070
OffchainAssetReceiptVaultConfigV2 memory config;
71-
bool eventFound = false; // Flag to indicate whether the event log was found
71+
bool eventFound = false;
72+
73+
address authorizeSetMsgSender;
74+
address authorizeSetTo;
75+
bool authorizeSetEventFound = false;
7276
for (uint256 i = 0; i < logs.length; i++) {
7377
if (
7478
logs[i].topics[0]
@@ -79,7 +83,10 @@ contract OffChainAssetReceiptVaultTest is OffchainAssetReceiptVaultTest {
7983
// Decode the event data
8084
(msgSender, config) = abi.decode(logs[i].data, (address, OffchainAssetReceiptVaultConfigV2));
8185
eventFound = true; // Set the flag to true since event log was found
82-
break;
86+
} else if (logs[i].topics[0] == keccak256("AuthorizerSet(address,address)")) {
87+
// Decode the event data
88+
(authorizeSetMsgSender, authorizeSetTo) = abi.decode(logs[i].data, (address, address));
89+
authorizeSetEventFound = true; // Set the flag to true since event log was found
8390
}
8491
}
8592

@@ -101,6 +108,12 @@ contract OffChainAssetReceiptVaultTest is OffchainAssetReceiptVaultTest {
101108
assertTrue(address(config.receiptVaultConfig.receipt) != address(0));
102109
assertEq(address(config.receiptVaultConfig.receipt), address(vault.receipt()));
103110

111+
/// Check the authorizer set event
112+
assertTrue(authorizeSetEventFound, "AuthorizerSet event log not found");
113+
assertEq(authorizeSetMsgSender, address(iFactory));
114+
assertEq(authorizeSetTo, address(vault));
115+
assertTrue(address(vault) != address(0));
116+
104117
// Check the receipt manager is the vault.
105118
assertEq(address(vault), vault.receipt().manager());
106119
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {
6+
ReceiptVault,
7+
ReceiptVaultConstructionConfigV2,
8+
IReceiptV3,
9+
ICloneableFactoryV2,
10+
IReceiptManagerV2,
11+
IReceiptVaultV3,
12+
ICloneableV2
13+
} from "src/abstract/ReceiptVault.sol";
14+
import {Test} from "forge-std/Test.sol";
15+
import {OffchainAssetReceiptVault, IAuthorizeV1} from "src/concrete/vault/OffchainAssetReceiptVault.sol";
16+
17+
import {IERC165} from "openzeppelin-contracts/contracts/utils/introspection/IERC165.sol";
18+
19+
contract OffchainAssetReceiptVaultIERC165Test is Test {
20+
function testOffchainAssetReceiptVaultIERC165(bytes4 badInterfaceId) external {
21+
OffchainAssetReceiptVault receiptVault = new OffchainAssetReceiptVault(
22+
ReceiptVaultConstructionConfigV2({
23+
factory: ICloneableFactoryV2(address(0)),
24+
receiptImplementation: IReceiptV3(address(0))
25+
})
26+
);
27+
vm.assume(badInterfaceId != type(IERC165).interfaceId);
28+
vm.assume(badInterfaceId != type(IReceiptManagerV2).interfaceId);
29+
vm.assume(badInterfaceId != type(IReceiptVaultV3).interfaceId);
30+
vm.assume(badInterfaceId != type(ICloneableV2).interfaceId);
31+
vm.assume(badInterfaceId != type(IAuthorizeV1).interfaceId);
32+
33+
assertTrue(receiptVault.supportsInterface(type(IERC165).interfaceId));
34+
assertTrue(receiptVault.supportsInterface(type(IReceiptManagerV2).interfaceId));
35+
assertTrue(receiptVault.supportsInterface(type(IReceiptVaultV3).interfaceId));
36+
assertTrue(receiptVault.supportsInterface(type(ICloneableV2).interfaceId));
37+
assertTrue(receiptVault.supportsInterface(type(IAuthorizeV1).interfaceId));
38+
39+
assertFalse(receiptVault.supportsInterface(badInterfaceId));
40+
}
41+
}

0 commit comments

Comments
 (0)