Skip to content

Commit 15cbe7e

Browse files
Merge pull request #274 from rainlanguage/2025-12-13-proxy-agnostic
wip on proxy agnostic
2 parents 94d812e + 2009cea commit 15cbe7e

33 files changed

+998
-735
lines changed

.gas-snapshot

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

script/Deploy.sol

Lines changed: 0 additions & 96 deletions
This file was deleted.

src/abstract/ReceiptVault.sol

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {
1313
LibFixedPointDecimalArithmeticOpenZeppelin,
1414
Math
1515
} from "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
16-
import {ICloneableFactoryV2} from "rain.factory/interface/ICloneableFactoryV2.sol";
17-
//forge-lint: disable-next-line(unused-import)
16+
// Export ICLONEABLE_V2_SUCCESS for concrete implementations.
17+
// forge-lint: disable-next-line(unused-import)
1818
import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "rain.factory/interface/ICloneableV2.sol";
1919
import {Address} from "openzeppelin-contracts/contracts/utils/Address.sol";
2020
import {
@@ -43,35 +43,17 @@ enum ShareAction {
4343
Burn
4444
}
4545

46-
/// Config for the _implementation_ of the `ReceiptVault` contract.
47-
/// @param factory The factory that will be used to clone the receipt vault.
48-
/// @param receiptImplementation The receipt implementation that will be cloned
49-
/// by the factory.
50-
struct ReceiptVaultConstructionConfigV2 {
51-
ICloneableFactoryV2 factory;
52-
IReceiptV3 receiptImplementation;
53-
}
54-
55-
/// All config required to initialize `ReceiptVault` except the receipt address.
56-
/// Included as a field on `ReceiptVaultConfig` which is the full initialization
57-
/// config struct. This is used by the `ReceiptVaultFactory` which will create a
58-
/// new receipt in the same transaction and build the full `ReceiptVaultConfig`.
46+
/// All config required to initialize `ReceiptVault`.
47+
/// @param receipt The `Receipt` e.g. built by `ReceiptVaultFactory` that is
48+
/// owned by the `ReceiptVault` as an `IReceiptOwnerV1`.
5949
/// @param asset As per ERC4626.
6050
/// @param name As per ERC20.
6151
/// @param symbol As per ERC20.
62-
struct VaultConfig {
52+
struct ReceiptVaultConfigV2 {
6353
address asset;
6454
string name;
6555
string symbol;
66-
}
67-
68-
/// All config required to initialize `ReceiptVault`.
69-
/// @param receipt The `Receipt` e.g. built by `ReceiptVaultFactory` that is
70-
/// owned by the `ReceiptVault` as an `IReceiptOwnerV1`.
71-
/// @param vaultConfig all the vault configuration as `VaultConfig`.
72-
struct ReceiptVaultConfig {
7356
address receipt;
74-
VaultConfig vaultConfig;
7557
}
7658

7759
/// @title ReceiptVault
@@ -104,12 +86,13 @@ struct ReceiptVaultConfig {
10486
/// with the shares' mint event DO NOT MOVE (whatever that means) until/unless
10587
/// those shares are burned.
10688
///
107-
/// Each vault is deployed from a factory as a clone from a reference
108-
/// implementation, allowing for the model to cheaply and freedomly scale
109-
/// horizontally. This allows for some trust/permissioned concessions to be made
110-
/// per-vault as new competing vaults can always be deployed and traded against
111-
/// each other in parallel, allowing trust to be "policed" at the liquidity and
112-
/// free market layer.
89+
/// Each vault is designed to be an implementation for proxies, either clones,
90+
/// beacons or transparent proxies, allowing for the model to cheaply and freely
91+
/// scale horizontally. The deployer is responsible for correctly initializing
92+
/// the proxies either way. This allows for some trust/permissioned concessions
93+
/// to be made per-vault as new competing vaults can always be deployed and
94+
/// traded against each other in parallel, allowing trust to be "policed" at the
95+
/// liquidity and free market layer.
11396
abstract contract ReceiptVault is
11497
IReceiptManagerV2,
11598
IReceiptVaultV3,
@@ -122,11 +105,6 @@ abstract contract ReceiptVault is
122105
using LibFixedPointDecimalArithmeticOpenZeppelin for uint256;
123106
using SafeERC20 for IERC20;
124107

125-
//slither-disable-next-line naming-convention
126-
ICloneableFactoryV2 internal immutable I_FACTORY;
127-
//slither-disable-next-line naming-convention
128-
IReceiptV3 internal immutable I_RECEIPT_IMPLEMENTATION;
129-
130108
/// @param asset Underlying ERC4626 asset.
131109
/// @param receipt ERC1155 Receipt owned by this receipt vault for the
132110
/// purpose of tracking mints and enforcing integrity of subsequent burns.
@@ -143,14 +121,8 @@ abstract contract ReceiptVault is
143121
}
144122
}
145123

146-
/// `ReceiptVault` is intended to be cloned and initialized by a
147-
/// `ReceiptVaultFactory` so is an implementation contract that can't itself
148-
/// be initialized.
149-
constructor(ReceiptVaultConstructionConfigV2 memory config) {
124+
constructor() {
150125
_disableInitializers();
151-
152-
I_FACTORY = config.factory;
153-
I_RECEIPT_IMPLEMENTATION = config.receiptImplementation;
154126
}
155127

156128
/// Deposits are payable so this allows refunds.
@@ -164,23 +136,15 @@ abstract contract ReceiptVault is
164136
// solhint-disable-next-line func-name-mixedcase
165137
// slither-disable-start naming-convention
166138
// forge-lint: disable-next-line(mixed-case-function)
167-
function __ReceiptVault_init(VaultConfig memory config) internal virtual {
139+
function __ReceiptVault_init(ReceiptVaultConfigV2 memory config) internal virtual {
168140
__Multicall_init();
169141
__ERC20_init(config.name, config.symbol);
170142

171-
// Slither false positive here due to it being impossible to set the
172-
// receipt before it has been deployed.
173-
// slither-disable-next-line reentrancy-benign
174-
IReceiptV3 managedReceipt =
175-
IReceiptV3(I_FACTORY.clone(address(I_RECEIPT_IMPLEMENTATION), abi.encode(address(this))));
176-
177143
ReceiptVaultV17201Storage storage s = getStorageReceiptVault();
178144
s.asset = IERC20(config.asset);
179-
s.receipt = managedReceipt;
145+
s.receipt = IReceiptV3(config.receipt);
180146

181-
// Sanity check here. Should always be true as we cloned the receipt
182-
// from the factory ourselves just above.
183-
address receiptManager = managedReceipt.manager();
147+
address receiptManager = IReceiptV3(config.receipt).manager();
184148
if (receiptManager != address(this)) {
185149
revert WrongManager(address(this), receiptManager);
186150
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
ZeroReceiptImplementation,
7+
ZeroVaultImplementation,
8+
InitializeNonZeroReceipt,
9+
InitializeReceiptFailed,
10+
InitializeVaultFailed
11+
} from "../../error/ErrDeployer.sol";
12+
import {Clones} from "openzeppelin-contracts/contracts/proxy/Clones.sol";
13+
import {Receipt, ICLONEABLE_V2_SUCCESS} from "../receipt/Receipt.sol";
14+
import {
15+
ERC20PriceOracleReceiptVault,
16+
ERC20PriceOracleReceiptVaultConfigV2
17+
} from "../vault/ERC20PriceOracleReceiptVault.sol";
18+
19+
/// Configuration for the ERC20PriceOracleReceiptVaultCloneDeployer construction.
20+
/// @param receiptImplementation The address of the Receipt implementation
21+
/// contract to clone from.
22+
/// @param erc20PriceOracleReceiptVaultImplementation The address of the
23+
/// ERC20PriceOracleReceiptVault implementation contract to clone from.
24+
//forge-lint: disable-next-line(pascal-case-struct)
25+
struct ERC20PriceOracleReceiptVaultCloneDeployerConfig {
26+
address receiptImplementation;
27+
address erc20PriceOracleReceiptVaultImplementation;
28+
}
29+
30+
/// @title ERC20PriceOracleReceiptVaultCloneDeployer
31+
/// Deploys ERC20PriceOracleReceiptVault contracts as minimal proxy contracts
32+
/// and handles the necessary initialization atomically.
33+
contract ERC20PriceOracleReceiptVaultCloneDeployer {
34+
/// Emitted when a new deployment is successfully initialized.
35+
/// @param sender The address that initiated the deployment.
36+
/// @param erc20PriceOracleReceiptVault The address of the deployed
37+
/// ERC20PriceOracleReceiptVault contract.
38+
/// @param receipt The address of the deployed Receipt contract.
39+
event ERC20PriceOracleReceiptVaultCloneDeployerDeployment(
40+
address sender, address erc20PriceOracleReceiptVault, address receipt
41+
);
42+
43+
/// The address of the Receipt implementation contract to clone from.
44+
address public immutable I_RECEIPT_IMPLEMENTATION;
45+
46+
/// The address of the ERC20PriceOracleReceiptVault implementation contract
47+
/// to clone from.
48+
address public immutable I_ERC20_PRICE_ORACLE_RECEIPT_VAULT_IMPLEMENTATION;
49+
50+
/// @param config The configuration for the deployer.
51+
constructor(ERC20PriceOracleReceiptVaultCloneDeployerConfig memory config) {
52+
if (config.receiptImplementation == address(0)) revert ZeroReceiptImplementation();
53+
if (config.erc20PriceOracleReceiptVaultImplementation == address(0)) revert ZeroVaultImplementation();
54+
I_RECEIPT_IMPLEMENTATION = config.receiptImplementation;
55+
I_ERC20_PRICE_ORACLE_RECEIPT_VAULT_IMPLEMENTATION = config.erc20PriceOracleReceiptVaultImplementation;
56+
}
57+
58+
/// Deploys and initializes a new ERC20PriceOracleReceiptVault contract
59+
/// along with its associated Receipt contract.
60+
/// @param config The configuration for the ERC20PriceOracleReceiptVault.
61+
/// @return The address of the newly deployed ERC20PriceOracleReceiptVault
62+
/// contract.
63+
function newERC20PriceOracleReceiptVault(ERC20PriceOracleReceiptVaultConfigV2 memory config)
64+
external
65+
returns (ERC20PriceOracleReceiptVault)
66+
{
67+
if (config.receiptVaultConfig.receipt != address(0)) {
68+
revert InitializeNonZeroReceipt(config.receiptVaultConfig.receipt);
69+
}
70+
71+
Receipt receipt = Receipt(Clones.clone(I_RECEIPT_IMPLEMENTATION));
72+
ERC20PriceOracleReceiptVault erc20PriceOracleReceiptVault =
73+
ERC20PriceOracleReceiptVault(payable(Clones.clone(I_ERC20_PRICE_ORACLE_RECEIPT_VAULT_IMPLEMENTATION)));
74+
75+
if (receipt.initialize(abi.encode(erc20PriceOracleReceiptVault)) != ICLONEABLE_V2_SUCCESS) {
76+
revert InitializeReceiptFailed();
77+
}
78+
79+
config.receiptVaultConfig.receipt = address(receipt);
80+
if (erc20PriceOracleReceiptVault.initialize(abi.encode(config)) != ICLONEABLE_V2_SUCCESS) {
81+
revert InitializeVaultFailed();
82+
}
83+
84+
emit ERC20PriceOracleReceiptVaultCloneDeployerDeployment(
85+
msg.sender, address(erc20PriceOracleReceiptVault), address(receipt)
86+
);
87+
88+
return erc20PriceOracleReceiptVault;
89+
}
90+
}

0 commit comments

Comments
 (0)