Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ code in this repository.

rain.factory is a Solidity **library** repo: the `ICloneable*` interface surface
for EIP1167 minimal proxy (clone) factories in the Rain ecosystem. It is the
library half of the library/deploy split (rainlanguage/rain.factory#46) and holds
interfaces only — no concrete contract, no deploy pins, no deploy script, and no
tests.
library half of the library/deploy split (rainlanguage/rain.factory#46) and
holds interfaces only — no concrete contract, no deploy pins, no deploy script,
and no tests.

The concrete `CloneFactory` that implements these interfaces, its deployed
address + codehash pins (`LibCloneFactoryDeploy`), the frozen
Expand Down Expand Up @@ -62,19 +62,38 @@ forge build
success.
- `src/interface/ICloneableFactoryV2.sol` — Legacy factory interface: the
nonce-dependent `clone(address, bytes)` and `NewClone` event. Superseded by
`ICloneableFactoryV3` for the concrete factory in rain.factory.deploy; still
published for other consumers.
- `src/interface/ICloneableFactoryV3.sol` — Current factory interface.
Deterministic-only: `cloneDeterministic(address, bytes, bytes32)` +
`ICloneableFactoryV3`/`ICloneableFactoryV4` for the concrete factory in
rain.factory.deploy; still published for other consumers.
- `src/interface/ICloneableFactoryV3.sol` — Deterministic-only factory
interface: `cloneDeterministic(address, bytes, bytes32)` +
`predictDeterministicAddress(address, bytes32, address)` (CREATE2, salt
namespaced by `msg.sender`) and its own `NewClone` event. Standalone — does
NOT extend `ICloneableFactoryV2`, because the non-deterministic `clone()` was
intentionally dropped.
intentionally dropped. Still published for consumers pinned to it.
- `src/interface/ICloneableFactoryV4.sol` — Current factory interface. Extends
`ICloneableFactoryV3` (nothing was dropped this time, so it inherits rather
than restates) and adds the open-salt variant:
`cloneDeterministicOpenSalt(address, bytes, bytes32)` +
`predictDeterministicAddressOpenSalt(address, bytes, bytes32)`. Their
`CREATE2` salt is
`keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data)))`
— the deployer is out of the derivation and the initialization data is in it,
so the address commits to what was deployed rather than to who deployed it,
and a front-runner can only either land elsewhere or produce the intended
contract. `ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN` is a file-level constant,
same pattern as `ICLONEABLE_V2_SUCCESS`; it keeps the open-salt image disjoint
from the inherited `cloneDeterministic` one, which does take arbitrary `data`.
The NatSpec on the function is the spec for what the address does NOT fix
(implementations MUST NOT read `tx.origin`) and for the registry pairing, and
it is the deliverable of this interface as much as the two signatures are.
- `src/interface/deprecated/` — Legacy interfaces (`ICloneableV1`,
`ICloneableFactoryV1`, `IFactory`). Do not use for new work.

`src/` holds nothing else. The interfaces import nothing — not each other, not a
third-party library — which is what makes this half a standalone publish.
`src/` holds nothing else. No interface here imports anything outside this repo
— no third-party library, no concrete contract — which is what makes this half a
standalone publish. Interface-to-interface inheritance within `src/interface/`
is allowed and `ICloneableFactoryV4` uses it (`is ICloneableFactoryV3`, via a
relative import).

## Solidity Conventions

Expand All @@ -86,10 +105,11 @@ third-party library — which is what makes this half a standalone publish.
- Optimizer: enabled, 100,000 runs
- No CBOR metadata (`cbor_metadata = false`, `bytecode_hash = "none"`)
- Dependencies are managed with Soldeer (`[dependencies]` in `foundry.toml` +
`soldeer.lock`, vendored under `dependencies/`). The interfaces import nothing,
so the only entry is forge-std. `@openzeppelin-contracts`, `rain-extrospection`,
`rain-deploy` and `rain-sol-codegen` went with the deploy half and must not
come back: adding one here means concrete code has landed in a library repo.
`soldeer.lock`, vendored under `dependencies/`). The interfaces import nothing
from outside this repo, so the only entry is forge-std.
`@openzeppelin-contracts`, `rain-extrospection`, `rain-deploy` and
`rain-sol-codegen` went with the deploy half and must not come back: adding
one here means concrete code has landed in a library repo.

## Deployment

Expand Down
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ possible on the implementation side.
The ideal would be that "any" contract can call an interpreter and magically be
supported but there's a lot that can go wrong, for example:

- Contracts can self destruct or even [redeployed with new bytecode](https://0age.medium.com/the-promise-and-the-peril-of-metamorphic-contracts-9eb8b8413c5e)
- Contracts can self destruct or even
[redeployed with new bytecode](https://0age.medium.com/the-promise-and-the-peril-of-metamorphic-contracts-9eb8b8413c5e)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
- Proxies can point to new implementations and "upgrade"
- Discoverability of ABIs and other metadata subject to indexer limitations

Expand All @@ -41,13 +42,33 @@ Falling short of the ideal, we want to support:
- Support existing patterns such as EIP1167 for clones, etc.
- Avoid introducing Rain-isms as much as possible

The onchain tooling for analysis is found at https://github.com/rainprotocol/rain.extrospection
The onchain tooling for analysis is found at
https://github.com/rainprotocol/rain.extrospection

The current interfaces in this repository are for

- `ICloneableFactoryV3`, the current factory interface: deterministic-only
(`cloneDeterministic` + `predictDeterministicAddress`, CREATE2 with the salt
namespaced by `msg.sender`). Standalone rather than extending
- `ICloneableFactoryV4`, the current factory interface. Extends
`ICloneableFactoryV3` — nothing was dropped this time, so it inherits rather
than restates — and adds a second deterministic derivation,
`cloneDeterministicOpenSalt` + `predictDeterministicAddressOpenSalt`, whose
`CREATE2` salt hashes the caller-supplied salt together with the
initialization data and nothing about the caller. The two derivations differ
in what the clone's address commits to, and neither dominates: the V3 pair
namespaces the salt by `msg.sender`, so the address commits to WHO deployed
and not to WHAT — nobody else can reach the caller's address, but the
deploying account is baked into it forever and the deployer alone decides the
initial state. The open-salt pair commits to WHAT and not to WHO — every
account reaches the same address, and so can anyone, but everyone who reaches
it deploys the same contract initialized with the same bytes, because varying
either input lands somewhere else. Its cost is that the address is not
knowable until the data is final. The residual the address cannot fix —
implementations MUST NOT read `tx.origin` — and the address-registry pairing
it is intended for are spelled out in the NatSpec on
`ICloneableFactoryV4.cloneDeterministicOpenSalt`
- `ICloneableFactoryV3`, deterministic-only (`cloneDeterministic` +
`predictDeterministicAddress`, CREATE2 with the salt namespaced by
`msg.sender`). Superseded by `ICloneableFactoryV4`, still published for
consumers pinned to it. Standalone rather than extending
`ICloneableFactoryV2`, because the non-deterministic `clone()` was
intentionally dropped
- `ICloneableFactoryV2` that is expected to clone proxies from a reference
Expand All @@ -61,9 +82,9 @@ The current interfaces in this repository are for

#### `ICloneableV1`

This version of `ICloneable` did not have any explicit return value on success of
initialize. It is possible for contracts that do not implement `ICloneableV1` to
silently fail to initialize when cloned by an `ICloneableFactoryV1`.
This version of `ICloneable` did not have any explicit return value on success
of initialize. It is possible for contracts that do not implement `ICloneableV1`
to silently fail to initialize when cloned by an `ICloneableFactoryV1`.

Newer versions of the interface include an explicit success value and check.

Expand All @@ -80,4 +101,4 @@ This was suboptimal for several reasons:
- Redundant work to maintain a growing list of factories

The legacy interface is available as `IFactory` but it is NOT RECOMMENDED for
new contracts.
new contracts.
197 changes: 197 additions & 0 deletions src/interface/ICloneableFactoryV4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.18;

import {ICloneableFactoryV3} from "./ICloneableFactoryV3.sol";

/// @dev Domain separator mixed into every `cloneDeterministicOpenSalt` effective
/// salt. Its only job is to keep the open-salt derivation's image disjoint from
/// every other derivation the same factory offers, so no other entry point on
/// the factory can be aimed at an open-salt address. See `ICloneableFactoryV4`.
bytes32 constant ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN = keccak256("ICloneableFactoryV4.cloneDeterministicOpenSalt");

/// @title ICloneableFactoryV4
/// @notice Extends `ICloneableFactoryV3` with an "open salt" deterministic
/// clone. Everything `ICloneableFactoryV3` specifies is unchanged and still
/// required — `cloneDeterministic` keeps namespacing its salt by `msg.sender`,
/// and `predictDeterministicAddress` keeps taking a `deployer`. This interface
/// only ADDS a second derivation alongside it, so a factory may offer both and
/// the caller picks per deploy.
///
/// The difference between the two is which of the deployer and the
/// initialization data the clone's address commits to:
///
/// - `cloneDeterministic` derives the `CREATE2` salt from `(msg.sender, salt)`.
/// The address commits to WHO deployed and not to WHAT was deployed. It buys
/// squat-resistance — nobody but that account can reach that address — and
/// pays with an identity baked into an address: retire the deploying account
/// and every address derived from it becomes unreachable, so a pinned address
/// can never be re-established from a different account. It also leaves
/// `data` outside the derivation, so the deployer alone decides the clone's
/// initial state at an address that says nothing about it.
/// - `cloneDeterministicOpenSalt` derives the `CREATE2` salt from
/// `(salt, data)`. The address commits to WHAT was deployed and not to WHO
/// deployed it. Every account reaches the same address — and so can anyone —
/// but every account that reaches it deploys the same contract, initialized
/// with the same bytes, because varying either input lands somewhere else.
///
/// Neither dominates. Open-salt costs the ability to choose an address before
/// the initialization data is final: the address is not knowable until `data`
/// is, and re-deploying "the same" clone with corrected `data` is a different
/// address. Sender-namespacing costs portability across accounts. A consumer
/// pinning an open-salt address must be able to reproduce the exact `data`
/// bytes, ABI encoding and all, since a byte of difference is a different
/// address.
///
/// The open-salt effective `CREATE2` salt is fixed by this interface as
///
/// ```
/// keccak256(abi.encode(
/// ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data)
/// ))
/// ```
///
/// so third parties can recompute it, and `predictDeterministicAddressOpenSalt`
/// is the factory saying the same thing.
///
/// Cross-network determinism is NOT a property of either derivation on its own.
/// `CREATE2` hashes the deploying factory's address, and the EIP-1167 creation
/// code it hashes contains the implementation's address, so an open-salt clone
/// is at the same address on two chains only when BOTH the factory and the
/// implementation are at the same address on both — each deployed
/// deterministically (Zoltu-style), all the way down. Dropping `msg.sender` from
/// the derivation removes the deployer as a third thing that has to match; it
/// does not make the other two match. If the implementation is deployed by an
/// ordinary nonce-dependent `CREATE` on each chain, its address differs per
/// chain and so does every clone of it, on both derivations.
interface ICloneableFactoryV4 is ICloneableFactoryV3 {
/// Deploys an EIP-1167 proxy clone of `implementation` via `CREATE2` at an
/// address that does not depend on the caller and does depend on the
/// initialization data.
///
/// The factory MUST use
/// `keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data)))`
/// as the `CREATE2` salt, so the deployed address is a pure function of
/// `(factory, implementation, salt, data)`. The factory MUST NOT mix
/// `msg.sender`, `tx.origin`, or any other caller-derived value into it.
///
/// Initialization is unchanged from `ICloneableFactoryV3.cloneDeterministic`
/// and MUST stay atomic with the clone: the factory MUST call
/// `ICloneableV2.initialize` with `data` verbatim, MUST NOT call anything
/// else on the proxy first, and MUST ONLY consider the clone created if
/// `initialize` returns keccak256("ICloneableV2.initialize"). MUST emit
/// `NewClone`.
///
/// # Why hashing `data` into the salt is the whole point
///
/// Without the `msg.sender` namespacing, anybody can deploy at this address
/// before the party that intended to, and since clone-and-initialize is
/// atomic and `initialize` runs exactly once, whoever gets there first sets
/// the clone's state permanently. There is no recovery: the address is
/// occupied and nobody can redeploy over it.
///
/// That is only dangerous if the first deployer has anything to vary.
/// Because `data` is inside the derivation, they do not:
///
/// - A front-runner passing DIFFERENT `data` derives a DIFFERENT address.
/// The address anyone pinned is untouched; the front-runner has deployed
/// their own contract at their own address, at their own expense.
/// - A front-runner passing the SAME `data` produces the contract that was
/// intended, initialized with the bytes that were intended, and has done
/// nothing but pay the gas.
///
/// This is the same position that makes permissionless deterministic
/// (Zoltu-style) deployment harmless — a Zoltu deploy has no arguments, so
/// front-running it produces byte-for-byte the intended contract — reached
/// WITH arguments, by putting the arguments in the address rather than by
/// having none. It is a property of this signature, not a condition on the
/// implementation being cloned, so there is no per-implementation audit of
/// "could a squatter pass something worse" to get wrong.
///
/// # What the address does NOT fix, which implementations MUST respect
///
/// The address fixes `data`. It cannot fix anything `initialize` reads that
/// is not `data`, so the deployer keeps exactly one lever: WHEN the deploy
/// lands, and therefore which chain state `initialize` observes.
///
/// - The implementation MUST NOT read `tx.origin`, directly or through
/// anything it calls during initialization. `tx.origin` is the deployer,
/// and it is the one remaining channel by which the deployer could reach
/// initial state. (`msg.sender` during `initialize` is the factory, which
/// is the same for every caller and therefore harmless.)
/// - Anything else `initialize` resolves from chain state resolves the same
/// way for every caller at a given block. An address registry — such as
/// rain.deploy's — is the intended shape here: `initialize` resolves the
/// admin by NAME from the registry rather than taking an admin address,
/// and the name, being part of `data`, is committed to by the address.
/// A front-runner resolves the same admin the intended deployer would
/// have. While the name is unbound the registry read reverts, so the
/// clone cannot be deployed at all, and the front-running window only
/// opens once the binding exists. A clone that resolves once during
/// `initialize` and stores the answer is unaffected by any later
/// rebinding.
/// - Registry-resolved authority is now the ordinary case rather than a
/// special one: it is simply `data` that names things instead of naming
/// addresses. `data` MAY be empty, and an implementation that resolves
/// everything from the registry will pass empty `data`.
///
/// # Obligation on the factory, not on the consumer
///
/// The guarantee above holds only while no OTHER entry point on the same
/// factory can `CREATE2` at an effective salt in this derivation's image
/// with caller-supplied initialization data. A factory implementing this
/// interface MUST NOT expose one. That is what
/// `ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN` is for: it separates this
/// derivation from the inherited `cloneDeterministic` one — which does take
/// arbitrary `data` — by both a domain tag and a preimage length, so
/// aiming `cloneDeterministic` at an open-salt address requires a keccak256
/// preimage rather than a choice of salt.
///
/// # Events
///
/// `NewClone` is shared with `cloneDeterministic` and is emitted
/// identically, with the caller-supplied `salt` — NOT the effective salt.
/// Its `sender` field is only whoever paid for this deploy and is not part
/// of the address derivation, but `salt` and `data` together are the whole
/// of it, so the event still carries the full deterministic deploy that
/// `ICloneableFactoryV3.NewClone` promises. An indexer that wants to verify
/// the address rather than take the emitted one MUST pick the derivation:
/// the two cannot both produce the emitted `clone`, so trying both and
/// keeping the match is well defined.
///
/// @param implementation The contract to clone.
/// @param data As per `ICloneableV2`, and part of the address derivation.
/// MAY be empty.
/// @param salt Caller-chosen salt. Distinguishes clones that share an
/// implementation and `data`; distinct `(salt, data)` pairs yield distinct
/// clones.
/// @return New child contract address.
function cloneDeterministicOpenSalt(address implementation, bytes calldata data, bytes32 salt)
external
returns (address);

/// The address `cloneDeterministicOpenSalt(implementation, data, salt)`
/// deploys to. Takes `data` because `data` is in the derivation, and takes
/// no `deployer` because the deployer is not — that is the entire
/// difference from `predictDeterministicAddress`. A pure function of its
/// inputs and this factory, so it is computable (and pinnable) before
/// deploying, by anyone. Identical across chains only where both this
/// factory and `implementation` are at the same address on each — see the
/// cross-network note on this interface.
///
/// A non-zero code size at the returned address means this exact
/// `(implementation, data, salt)` has already been deployed by somebody and
/// `cloneDeterministicOpenSalt` will revert there. Since nothing else can
/// be deployed there, what occupies it is the clone that was asked for,
/// initialized with the bytes that were asked for.
///
/// @param implementation The contract to clone.
/// @param data The initialization data that will be passed to
/// `ICloneableV2.initialize`.
/// @param salt The caller-chosen salt.
/// @return The predicted clone address.
function predictDeterministicAddressOpenSalt(address implementation, bytes calldata data, bytes32 salt)
external
view
returns (address);
}
Loading