Skip to content

Commit ccee424

Browse files
Merge pull request #229 from gildlab/2025-03-10-consolidate-free
2025 03 10 consolidate freeze handler
2 parents c688da2 + 9858d0e commit ccee424

35 files changed

+968
-411
lines changed

.gas-snapshot

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

script/Deploy.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
import {VaultConfig} from "src/abstract/ReceiptVault.sol";
1111
import {ICloneableFactoryV2} from "rain.factory/interface/ICloneableFactoryV2.sol";
1212
import {
13-
OffchainAssetReceiptVault, ReceiptVaultConstructionConfig
13+
OffchainAssetReceiptVault,
14+
ReceiptVaultConstructionConfigV2
1415
} from "src/concrete/vault/OffchainAssetReceiptVault.sol";
1516
import {Receipt as ReceiptContract} from "src/concrete/receipt/Receipt.sol";
1617
import {SceptreStakedFlrOracle} from "src/concrete/oracle/SceptreStakedFlrOracle.sol";
@@ -33,7 +34,7 @@ contract Deploy is Script {
3334
vm.startBroadcast(deploymentKey);
3435

3536
ReceiptContract receipt = new ReceiptContract();
36-
ReceiptVaultConstructionConfig memory receiptVaultConstructionConfig = ReceiptVaultConstructionConfig({
37+
ReceiptVaultConstructionConfigV2 memory receiptVaultConstructionConfig = ReceiptVaultConstructionConfigV2({
3738
factory: ICloneableFactoryV2(vm.envAddress("CLONE_FACTORY")),
3839
receiptImplementation: receipt
3940
});

src/abstract/ReceiptVault.sol

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {SafeERC20Upgradeable as SafeERC20} from
1111
"openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol";
1212
import {MulticallUpgradeable as Multicall} from
1313
"openzeppelin-contracts-upgradeable/contracts/utils/MulticallUpgradeable.sol";
14-
import {IReceiptVaultV2, IReceiptVaultV1, IReceiptV2} from "../interface/IReceiptVaultV2.sol";
14+
import {IReceiptVaultV3, IReceiptVaultV1, IReceiptV3} from "../interface/IReceiptVaultV3.sol";
1515
import {IReceiptManagerV2} from "../interface/IReceiptManagerV2.sol";
1616
import {
1717
LibFixedPointDecimalArithmeticOpenZeppelin,
@@ -42,9 +42,9 @@ enum ShareAction {
4242
/// @param factory The factory that will be used to clone the receipt vault.
4343
/// @param receiptImplementation The receipt implementation that will be cloned
4444
/// by the factory.
45-
struct ReceiptVaultConstructionConfig {
45+
struct ReceiptVaultConstructionConfigV2 {
4646
ICloneableFactoryV2 factory;
47-
IReceiptV2 receiptImplementation;
47+
IReceiptV3 receiptImplementation;
4848
}
4949

5050
/// All config required to initialize `ReceiptVault` except the receipt address.
@@ -110,25 +110,25 @@ abstract contract ReceiptVault is
110110
Multicall,
111111
ReentrancyGuard,
112112
ERC20,
113-
IReceiptVaultV2,
113+
IReceiptVaultV3,
114114
ICloneableV2
115115
{
116116
using LibFixedPointDecimalArithmeticOpenZeppelin for uint256;
117117
using SafeERC20 for IERC20;
118118

119119
ICloneableFactoryV2 internal immutable iFactory;
120-
IReceiptV2 internal immutable iReceiptImplementation;
120+
IReceiptV3 internal immutable iReceiptImplementation;
121121

122122
/// Underlying ERC4626 asset.
123123
IERC20 internal sAsset;
124124
/// ERC1155 Receipt owned by this receipt vault for the purpose of tracking
125125
/// mints and enforcing integrity of subsequent burns.
126-
IReceiptV2 internal sReceipt;
126+
IReceiptV3 internal sReceipt;
127127

128128
/// `ReceiptVault` is intended to be cloned and initialized by a
129129
/// `ReceiptVaultFactory` so is an implementation contract that can't itself
130130
/// be initialized.
131-
constructor(ReceiptVaultConstructionConfig memory config) {
131+
constructor(ReceiptVaultConstructionConfigV2 memory config) {
132132
_disableInitializers();
133133

134134
iFactory = config.factory;
@@ -154,8 +154,8 @@ abstract contract ReceiptVault is
154154
// Slither false positive here due to it being impossible to set the
155155
// receipt before it has been deployed.
156156
// slither-disable-next-line reentrancy-benign
157-
IReceiptV2 managedReceipt =
158-
IReceiptV2(iFactory.clone(address(iReceiptImplementation), abi.encode(address(this))));
157+
IReceiptV3 managedReceipt =
158+
IReceiptV3(iFactory.clone(address(iReceiptImplementation), abi.encode(address(this))));
159159
sReceipt = managedReceipt;
160160

161161
// Sanity check here. Should always be true as we cloned the receipt
@@ -172,14 +172,17 @@ abstract contract ReceiptVault is
172172
}
173173

174174
/// @inheritdoc IReceiptManagerV2
175-
function authorizeReceiptTransfer3(address from, address to, uint256[] memory ids, uint256[] memory amounts)
176-
public
177-
virtual
178-
{
175+
function authorizeReceiptTransfer3(
176+
address operator,
177+
address from,
178+
address to,
179+
uint256[] memory ids,
180+
uint256[] memory amounts
181+
) public virtual {
179182
if (msg.sender != address(receipt())) {
180183
revert UnmanagedReceiptTransfer();
181184
}
182-
(from, to, ids, amounts);
185+
(operator, from, to, ids, amounts);
183186
}
184187

185188
/// The spec demands that this function ignores per-user concerns. It seems
@@ -339,8 +342,8 @@ abstract contract ReceiptVault is
339342
);
340343
}
341344

342-
/// @inheritdoc IReceiptVaultV2
343-
function receipt() public view virtual returns (IReceiptV2) {
345+
/// @inheritdoc IReceiptVaultV3
346+
function receipt() public view virtual returns (IReceiptV3) {
344347
return sReceipt;
345348
}
346349

src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@ bytes32 constant DEPOSIT_ADMIN = keccak256("DEPOSIT_ADMIN");
4646
/// @dev Rolename for withdraw admins.
4747
bytes32 constant WITHDRAW_ADMIN = keccak256("WITHDRAW_ADMIN");
4848

49-
/// @dev Rolename for handlers.
50-
/// Handler role is required to accept tokens during system freeze.
51-
bytes32 constant FREEZE_HANDLER = keccak256("FREEZE_HANDLER");
52-
/// @dev Rolename for handler admins.
53-
bytes32 constant FREEZE_HANDLER_ADMIN = keccak256("FREEZE_HANDLER_ADMIN");
54-
5549
/// @dev Configuration for the OffchainAssetReceiptVaultAuthorizorV1.
5650
/// @param initialAdmin The initial admin of the contract.
5751
/// @param authorizee The address that is authorized to perform actions.
@@ -106,15 +100,11 @@ contract OffchainAssetReceiptVaultAuthorizerV1 is IAuthorizeV1, ICloneableV2, Ac
106100
_setRoleAdmin(WITHDRAW, WITHDRAW_ADMIN);
107101
_setRoleAdmin(WITHDRAW_ADMIN, WITHDRAW_ADMIN);
108102

109-
_setRoleAdmin(FREEZE_HANDLER, FREEZE_HANDLER_ADMIN);
110-
_setRoleAdmin(FREEZE_HANDLER_ADMIN, FREEZE_HANDLER_ADMIN);
111-
112103
_grantRole(CERTIFY_ADMIN, config.initialAdmin);
113104
_grantRole(CONFISCATE_RECEIPT_ADMIN, config.initialAdmin);
114105
_grantRole(CONFISCATE_SHARES_ADMIN, config.initialAdmin);
115106
_grantRole(DEPOSIT_ADMIN, config.initialAdmin);
116107
_grantRole(WITHDRAW_ADMIN, config.initialAdmin);
117-
_grantRole(FREEZE_HANDLER_ADMIN, config.initialAdmin);
118108

119109
return ICLONEABLE_V2_SUCCESS;
120110
}
@@ -173,13 +163,6 @@ contract OffchainAssetReceiptVaultAuthorizerV1 is IAuthorizeV1, ICloneableV2, Ac
173163

174164
// Everyone else can only transfer while the certification is valid.
175165
if (isCertificationExpired) {
176-
// Handlers can ALWAYS send and receive funds.
177-
// Handlers bypass BOTH the timestamp on certification AND tier based
178-
// restriction.
179-
if (hasRole(FREEZE_HANDLER, from) || hasRole(FREEZE_HANDLER, to)) {
180-
return;
181-
}
182-
183166
// Minting and burning is always allowed for the respective roles if they
184167
// interact directly with the shares/receipt. Minting and burning is ALSO
185168
// valid after the certification expires as it is likely the only way to
@@ -188,10 +171,13 @@ contract OffchainAssetReceiptVaultAuthorizerV1 is IAuthorizeV1, ICloneableV2, Ac
188171
return;
189172
}
190173

191-
// Confiscation is always allowed as it likely represents some kind of
192-
// regulatory/legal requirement. It may also be required to satisfy
193-
// certification requirements.
194-
if (hasRole(CONFISCATE_SHARES, to) || hasRole(CONFISCATE_RECEIPT, to)) {
174+
// Confiscators bypass the certification check when they are the
175+
// user. This allows for legal confiscation during system freeze
176+
// and for certification repair.
177+
if (
178+
(permission == TRANSFER_SHARES && hasRole(CONFISCATE_SHARES, user))
179+
|| (permission == TRANSFER_RECEIPT && hasRole(CONFISCATE_RECEIPT, user))
180+
) {
195181
return;
196182
}
197183

src/concrete/receipt/Receipt.sol

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ pragma solidity =0.8.25;
55
import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "rain.factory/interface/ICloneableV2.sol";
66

77
import {IReceiptManagerV2} from "../../interface/IReceiptManagerV2.sol";
8-
import {IReceiptV2} from "../../interface/IReceiptV2.sol";
9-
import {IReceiptVaultV2} from "../../interface/IReceiptVaultV2.sol";
8+
import {IReceiptV3} from "../../interface/IReceiptV3.sol";
9+
import {IReceiptVaultV3} from "../../interface/IReceiptVaultV3.sol";
1010
import {OnlyManager} from "../../error/ErrReceipt.sol";
1111
import {ERC1155Upgradeable as ERC1155} from
1212
"openzeppelin-contracts-upgradeable/contracts/token/ERC1155/ERC1155Upgradeable.sol";
@@ -25,15 +25,17 @@ string constant RECEIPT_NAME_SUFFIX = " Receipt";
2525
string constant RECEIPT_SYMBOL_SUFFIX = " RCPT";
2626

2727
/// @title Receipt
28-
/// @notice The `IReceiptV2` for a `ReceiptVault`. Standard implementation allows
28+
/// @notice The `IReceiptV3` for a `ReceiptVault`. Standard implementation allows
2929
/// receipt information to be emitted and mints/burns according to manager
3030
/// authorization.
31-
contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
31+
contract Receipt is IReceiptV3, ERC1155, ICloneableV2 {
3232
/// The manager of the `Receipt` contract.
3333
/// Set during `initialize` and cannot be changed.
3434
/// Intended to be a `ReceiptVault` contract.
3535
IReceiptManagerV2 internal sManager;
3636

37+
address private sSender = address(0);
38+
3739
/// Disables initializers so that the clonable implementation cannot be
3840
/// initialized and used directly outside a factory deployment.
3941
constructor() {
@@ -48,6 +50,17 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
4850
_;
4951
}
5052

53+
modifier withSender(address sender) {
54+
sSender = sender;
55+
_;
56+
sSender = address(0);
57+
}
58+
59+
function _msgSender() internal view virtual override returns (address) {
60+
address sender = sSender;
61+
return sender == address(0) ? msg.sender : sender;
62+
}
63+
5164
/// Initializes the `Receipt` so that it is usable as a clonable
5265
/// implementation in `ReceiptFactory`.
5366
/// Compatible with `ICloneableV2`.
@@ -80,12 +93,12 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
8093
return string.concat(DATA_URI_BASE64_PREFIX, Base64.encode(json));
8194
}
8295

83-
/// @inheritdoc IReceiptV2
96+
/// @inheritdoc IReceiptV3
8497
function name() public view virtual returns (string memory) {
8598
return string.concat(_vaultShareSymbol(), RECEIPT_NAME_SUFFIX);
8699
}
87100

88-
/// @inheritdoc IReceiptV2
101+
/// @inheritdoc IReceiptV3
89102
function symbol() external view virtual returns (string memory) {
90103
return string.concat(_vaultShareSymbol(), RECEIPT_SYMBOL_SUFFIX);
91104
}
@@ -101,40 +114,45 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
101114
/// managing this `Receipt` is accepting for mints. Can be overridden if the
102115
/// manager is not going to be a `ReceiptVault`.
103116
function _vaultAssetSymbol() internal view virtual returns (string memory) {
104-
return IERC20Metadata(IReceiptVaultV2(payable(address(sManager))).asset()).symbol();
117+
return IERC20Metadata(IReceiptVaultV3(payable(address(sManager))).asset()).symbol();
105118
}
106119

107-
/// @inheritdoc IReceiptV2
120+
/// @inheritdoc IReceiptV3
108121
function manager() external view virtual returns (address) {
109122
return address(sManager);
110123
}
111124

112-
/// @inheritdoc IReceiptV2
125+
/// @inheritdoc IReceiptV3
113126
function managerMint(address sender, address account, uint256 id, uint256 amount, bytes memory data)
114127
external
115128
virtual
116129
onlyManager
130+
withSender(sender)
117131
{
118132
_receiptInformation(sender, id, data);
119133
_mint(account, id, amount, data);
120134
}
121135

122-
/// @inheritdoc IReceiptV2
136+
/// @inheritdoc IReceiptV3
123137
function managerBurn(address sender, address account, uint256 id, uint256 amount, bytes memory data)
124138
external
125139
virtual
126140
onlyManager
141+
withSender(sender)
127142
{
128143
_receiptInformation(sender, id, data);
129144
_burn(account, id, amount);
130145
}
131146

132-
/// @inheritdoc IReceiptV2
133-
function managerTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data)
134-
external
135-
virtual
136-
onlyManager
137-
{
147+
/// @inheritdoc IReceiptV3
148+
function managerTransferFrom(
149+
address sender,
150+
address from,
151+
address to,
152+
uint256 id,
153+
uint256 amount,
154+
bytes memory data
155+
) external virtual onlyManager withSender(sender) {
138156
_safeTransferFrom(from, to, id, amount, data);
139157
}
140158

@@ -150,7 +168,7 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
150168
bytes memory data
151169
) internal virtual override {
152170
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
153-
sManager.authorizeReceiptTransfer3(from, to, ids, amounts);
171+
sManager.authorizeReceiptTransfer3(operator, from, to, ids, amounts);
154172
}
155173

156174
/// Emits `ReceiptInformation` if there is any data.
@@ -164,7 +182,7 @@ contract Receipt is IReceiptV2, ERC1155, ICloneableV2 {
164182
}
165183
}
166184

167-
/// @inheritdoc IReceiptV2
185+
/// @inheritdoc IReceiptV3
168186
function receiptInformation(uint256 id, bytes memory data) external virtual {
169187
_receiptInformation(msg.sender, id, data);
170188
}

src/concrete/vault/ERC20PriceOracleReceiptVault.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
ReceiptVault,
1111
ShareAction,
1212
ICLONEABLE_V2_SUCCESS,
13-
ReceiptVaultConstructionConfig
13+
ReceiptVaultConstructionConfigV2
1414
} from "../../abstract/ReceiptVault.sol";
1515
import {IPriceOracleV2} from "../../interface/IPriceOracleV2.sol";
1616

@@ -90,7 +90,7 @@ contract ERC20PriceOracleReceiptVault is ReceiptVault {
9090
/// The price oracle used for all minting calculations.
9191
IPriceOracleV2 public priceOracle;
9292

93-
constructor(ReceiptVaultConstructionConfig memory config) ReceiptVault(config) {}
93+
constructor(ReceiptVaultConstructionConfigV2 memory config) ReceiptVault(config) {}
9494

9595
/// Initialization of the underlying receipt vault and price oracle.
9696
function initialize(bytes memory data) public virtual override initializer returns (bytes32) {

src/concrete/vault/OffchainAssetReceiptVault.sol

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import {
99
ShareAction,
1010
InvalidId,
1111
ICLONEABLE_V2_SUCCESS,
12-
ReceiptVaultConstructionConfig
12+
ReceiptVaultConstructionConfigV2
1313
} from "../../abstract/ReceiptVault.sol";
1414
import {OwnableUpgradeable as Ownable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
15-
import {IReceiptV2} from "../../interface/IReceiptV2.sol";
15+
import {IReceiptV3} from "../../interface/IReceiptV3.sol";
1616
import {MathUpgradeable as Math} from "openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol";
1717
import {ITierV2} from "rain.tier.interface/interface/ITierV2.sol";
1818
import {IAuthorizeV1, Unauthorized} from "../../interface/IAuthorizeV1.sol";
@@ -289,7 +289,7 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
289289
/// be certified to a future time.
290290
uint256 internal sCertifiedUntil;
291291

292-
constructor(ReceiptVaultConstructionConfig memory config) ReceiptVault(config) {}
292+
constructor(ReceiptVaultConstructionConfigV2 memory config) ReceiptVault(config) {}
293293

294294
/// Initializes the initial admin and the underlying `ReceiptVault`.
295295
/// The admin provided will be admin of all roles and can reassign and revoke
@@ -356,14 +356,16 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
356356

357357
/// Apply standard transfer restrictions to receipt transfers.
358358
/// @inheritdoc ReceiptVault
359-
function authorizeReceiptTransfer3(address from, address to, uint256[] memory ids, uint256[] memory amounts)
360-
public
361-
virtual
362-
override
363-
{
364-
super.authorizeReceiptTransfer3(from, to, ids, amounts);
359+
function authorizeReceiptTransfer3(
360+
address operator,
361+
address from,
362+
address to,
363+
uint256[] memory ids,
364+
uint256[] memory amounts
365+
) public virtual override {
366+
super.authorizeReceiptTransfer3(operator, from, to, ids, amounts);
365367
sAuthorizer.authorize(
366-
msg.sender,
368+
operator,
367369
TRANSFER_RECEIPT,
368370
abi.encode(
369371
TransferReceiptStateChange({
@@ -575,7 +577,7 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
575577
sAuthorizer.authorize(msg.sender, CERTIFY, abi.encode(certifyStateChange));
576578
}
577579

578-
function isCertificationExpired() internal view returns (bool) {
580+
function isCertificationExpired() public view returns (bool) {
579581
return block.timestamp > sCertifiedUntil;
580582
}
581583

@@ -692,7 +694,7 @@ contract OffchainAssetReceiptVault is ReceiptVault, IAuthorizeV1, Ownable {
692694
uint256 actualAmount = receipt().balanceOf(confiscatee, id).min(targetAmount);
693695
if (actualAmount > 0) {
694696
emit ConfiscateReceipt(msg.sender, confiscatee, id, targetAmount, actualAmount, data);
695-
receipt().managerTransferFrom(confiscatee, msg.sender, id, actualAmount, "");
697+
receipt().managerTransferFrom(msg.sender, confiscatee, msg.sender, id, actualAmount, "");
696698
}
697699

698700
sAuthorizer.authorize(

0 commit comments

Comments
 (0)