Skip to content

Commit

Permalink
feat(ct): add ChugSplashClaimer
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-goldman committed May 9, 2023
1 parent 4c2db15 commit e5b9f81
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 27 deletions.
6 changes: 6 additions & 0 deletions .changeset/heavy-paws-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/contracts': minor
'@chugsplash/core': patch
---

Add ChugSplashClaimer which will exist on L1
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"packages/demo"
],
"nohoist": [
"@chugsplash/contracts/@connext/interfaces",
"@chugsplash/{contracts,plugins}/forge-std",
"@chugsplash/{contracts,plugins}/ds-test",
"@chugsplash/{contracts,plugins}/@openzeppelin/contracts",
Expand Down
140 changes: 140 additions & 0 deletions packages/contracts/contracts/ChugSplashClaimer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ICrossChainAdapter } from "./interfaces/ICrossChainAdapter.sol";
import { ChugSplashRegistry } from "./ChugSplashRegistry.sol";
import { Version } from "./Semver.sol";

/**
* @title ChugSplashClaimer
*/
contract ChugSplashClaimer is Ownable {
event OrganizationIDClaimed(bytes32 indexed orgID, address owner);

event RegistrationInitiated(
bytes32 indexed orgID,
address indexed originEndpoint,
uint32 indexed destDomainID,
address owner,
address caller
);

event InitiatorApprovalChanged(bytes32 indexed orgID, address indexed initiator, bool approved);

event CrossChainAdapterChanged(
address indexed originEndpoint,
uint32 indexed destDomainID,
address crossChainAdapter
);

ChugSplashRegistry public immutable registry;

mapping(bytes32 => bool) public organizationIDs;

mapping(bytes32 => address) public orgIDOwners;

mapping(bytes32 => mapping(address => bool)) public approvedInitiators;

// Origin endpoint => destination Domain ID => crossChainAdapter
mapping(address => mapping(uint32 => address)) public crossChainAdapters;

/**
* @param _owner Address of the owner of the registry.
*/
constructor(address _owner, ChugSplashRegistry _registry) {
registry = _registry;
_transferOwnership(_owner);
}

function claimOrganizationID(bytes32 _orgID, address _owner) external {
require(!organizationIDs[_orgID], "ChugSplashClaimer: orgID already claimed");
organizationIDs[_orgID] = true;
orgIDOwners[_orgID] = _owner;
emit OrganizationIDClaimed(_orgID, _owner);
}

struct CrossChainMessageInfo {
address payable originEndpoint;
uint32 destDomainID;
uint256 relayerFee;
}

struct RegistrationInfo {
Version version;
address owner;
bytes managerInitializerData;
}

function initiateRegistration(
bytes32 _orgID,
CrossChainMessageInfo[] memory _messages,
RegistrationInfo[] memory _registrationInfo
) external payable {
require(
msg.sender == orgIDOwners[_orgID] || approvedInitiators[_orgID][msg.sender],
"ChugSplashClaimer: caller not approved"
);

for (uint i = 0; i < _messages.length; i++) {
CrossChainMessageInfo memory messageInfo = _messages[i];
RegistrationInfo memory registration = _registrationInfo[i];
Version memory version = registration.version;

address managerImpl = registry.versions(version.major, version.minor, version.patch);
require(
registry.managerImplementations(managerImpl),
"ChugSplashClaimer: invalid manager version"
);

address crossChainAdapter = crossChainAdapters[messageInfo.originEndpoint][
messageInfo.destDomainID
];
require(
crossChainAdapter != address(0),
"ChugSplashClaimer: invalid crossChain adapter"
);

bytes memory registryCalldata = abi.encodeCall(
ChugSplashRegistry.finalizeRegistration,
(_orgID, registration.owner, version, registration.managerInitializerData)
);

(bool success, ) = crossChainAdapter.delegatecall(
abi.encodeCall(
ICrossChainAdapter.initiateRegistration,
(
messageInfo.originEndpoint,
messageInfo.destDomainID,
messageInfo.relayerFee,
registryCalldata
)
)
);
require(success, "ChugSplashClaimer: failed to initiate registration");

emit RegistrationInitiated(
_orgID,
messageInfo.originEndpoint,
messageInfo.destDomainID,
registration.owner,
msg.sender
);
}
}

function setInitiatorApproval(bytes32 _orgID, address _initiator, bool _approved) external {
require(msg.sender == orgIDOwners[_orgID], "ChugSplashClaimer: caller not org ID owner");
approvedInitiators[_orgID][_initiator] = _approved;
emit InitiatorApprovalChanged(_orgID, _initiator, _approved);
}

function setCrossChainAdapter(
address _originEndpoint,
uint32 _destDomainID,
address _crossChainAdapter
) external onlyOwner {
crossChainAdapters[_originEndpoint][_destDomainID] = _crossChainAdapter;
emit CrossChainAdapterChanged(_originEndpoint, _destDomainID, _crossChainAdapter);
}
}
29 changes: 16 additions & 13 deletions packages/contracts/contracts/ChugSplashRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Semver, Version } from "./Semver.sol";
* find and index events that occur throughout the deployment process. Lastly, the owner of
* this contract is able to add support for new contract kinds (e.g. OpenZeppelin's
Transparent proxy). The owner can also new versions of the ChugSplashManager
implementation.
* implementation.
*
*/
contract ChugSplashRegistry is Ownable, Initializable {
Expand Down Expand Up @@ -48,17 +48,17 @@ contract ChugSplashRegistry is Ownable, Initializable {
mapping(uint => mapping(uint => mapping(uint => address))) public versions;

/**
* @notice Emitted whenever a new project is claimed.
* @notice Emitted whenever registration is finalized for a given organization ID.
*
* @param organizationID Organization ID that was claimed.
* @param organizationID Organization ID that was registered.
* @param claimer Address of the claimer of the project. This is equivalent to the
* `msg.sender`.
* @param managerImpl Address of the initial ChugSplashManager implementation for this
* project.
* @param owner Address of the initial owner of the project.
* @param retdata Return data from the ChugSplashManager initializer.
*/
event ChugSplashProjectClaimed(
event ChugSplashRegistrationFinalized(
bytes32 indexed organizationID,
address indexed claimer,
address indexed managerImpl,
Expand Down Expand Up @@ -128,19 +128,16 @@ contract ChugSplashRegistry is Ownable, Initializable {
}

/**
* @notice Claims a new project by deploying a new ChugSplashManagerProxy
* contract and setting the provided owner as the initial owner of the new project. It also
checks that the
* organization ID being claimed has not already been claimed by the caller, and that the
specified version
* of the ChugSplashManager is a valid implementation.
* @notice Finalizes the registration of an organization ID by deploying a new
ChugSplashManagerProxy contract and setting the provided owner as the initial owner of the
new project.
*
* @param _organizationID Organization ID to claim.
* @param _organizationID Organization ID being registered.
* @param _owner Initial owner for the new project.
* @param _version Version of the ChugSplashManager implementation.
* @param _data Any data to pass to the ChugSplashManager initializer.
*/
function claim(
function finalizeRegistration(
bytes32 _organizationID,
address _owner,
Version memory _version,
Expand Down Expand Up @@ -177,7 +174,13 @@ contract ChugSplashRegistry is Ownable, Initializable {
// Change manager proxy admin to the Org owner
managerProxy.changeAdmin(_owner);

emit ChugSplashProjectClaimed(_organizationID, msg.sender, managerImpl, _owner, retdata);
emit ChugSplashRegistrationFinalized(
_organizationID,
msg.sender,
managerImpl,
_owner,
retdata
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import { ICrossChainAdapter } from "../../interfaces/ICrossChainAdapter.sol";
import { IConnext } from "@connext/interfaces/core/IConnext.sol";

/**
* @title ConnextCrossChainAdapter
*/
contract ConnextCrossChainAdapter is ICrossChainAdapter {
address public immutable registry;

constructor(address _registry) {
registry = _registry;
}

function initiateRegistration(
address payable _originEndpoint,
uint32 _destDomainID,
uint256 _relayerFee,
bytes memory _calldata
) external {
IConnext(_originEndpoint).xcall{ value: _relayerFee }(
_destDomainID, // _destination: Domain ID of the destination chain
registry, // _to: address of the target contract on the destination chain
address(0), // _asset: address of the token contract (this is unused)
msg.sender, // _delegate: address that can revert or forceLocal on destination
0, // _amount: amount of tokens to transfer (this is unused)
0, // _slippage: this is unused
_calldata // _callData: the encoded calldata to send
);
}
}
14 changes: 14 additions & 0 deletions packages/contracts/contracts/interfaces/ICrossChainAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

/**
* @title ICrossChainAdapter
*/
interface ICrossChainAdapter {
function initiateRegistration(
address payable _originEndpoint,
uint32 _destinationDomainID,
uint256 _relayerFee,
bytes memory _calldata
) external;
}
3 changes: 2 additions & 1 deletion packages/contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ remappings = [
'@eth-optimism/contracts-bedrock/=node_modules/@eth-optimism/contracts-bedrock',
'@eth-optimism/contracts/=node_modules/@eth-optimism/contracts',
'forge-std/=node_modules/forge-std/src/',
'ds-test/=node_modules/ds-test/src/'
'ds-test/=node_modules/ds-test/src/',
'@connext/interfaces=node_modules/@connext/interfaces/'
]

gas_price=1
1 change: 1 addition & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"url": "https://github.com/smartcontracts/chugsplash.git"
},
"devDependencies": {
"@connext/interfaces": "^2.0.5",
"@eth-optimism/core-utils": "^0.9.0",
"@openzeppelin/contracts": "^4.8.1",
"@openzeppelin/contracts-upgradeable": "^4.6.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const trackExecuted = async (
)
}

export const trackClaimed = async (
export const trackRegistrationFinalized = async (
user_id: string,
organizationID: string,
projectName: string,
Expand All @@ -51,7 +51,7 @@ export const trackClaimed = async (
}
await timeout(
await amplitudeClient.logEvent({
event_type: 'chugsplash claim',
event_type: 'chugsplash registration finalized',
user_id,
event_properties: {
organizationID,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ npx hardhat chugsplash-claim --network <network> --owner <ownerAddress> --config
throw new Error(`This project has not been claimed on ${networkName}.
To claim the project on this network, call the claim function from your script:
chugsplash.claim("${configPath}");
chugsplash.finalizeRegistration("${configPath}");
`)
}
}
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
isUUPSProxy,
readBuildInfo,
readCanonicalConfig,
claimChugSplashProject,
finalizeRegistration,
writeCanonicalConfig,
} from '../utils'
import { ArtifactPaths, getMinimumCompilerInput } from '../languages'
Expand Down Expand Up @@ -64,7 +64,7 @@ import {
trackDeployed,
trackListProjects,
trackProposed,
trackClaimed,
trackRegistrationFinalized,
trackImportProxy,
} from '../analytics'
import {
Expand Down Expand Up @@ -106,7 +106,7 @@ export const chugsplashClaimAbstractTask = async (
)
}

const isFirstTimeClaimed = await claimChugSplashProject(
const isFirstTimeClaimed = await finalizeRegistration(
provider,
claimer,
organizationID,
Expand All @@ -116,7 +116,7 @@ export const chugsplashClaimAbstractTask = async (

const networkName = await resolveNetworkName(provider, integration)

await trackClaimed(
await trackRegistrationFinalized(
await getProjectOwnerAddress(
getChugSplashManager(provider, claimerAddress, organizationID)
),
Expand Down Expand Up @@ -601,7 +601,7 @@ export const chugsplashDeployAbstractTask = async (
spinner.start(`Claiming ${projectName}...`)
// Claim the project with the signer as the owner. Once we've completed the deployment, we'll
// transfer ownership to the project owner specified in the config.
await claimChugSplashProject(
await finalizeRegistration(
provider,
signer,
organizationID,
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,15 @@ export const getChugSplashManagerAddress = (
}

/**
* Claims a new ChugSplash project.
* Finalizes the registration of an organization ID.
*
* @param Provider Provider corresponding to the signer that will execute the transaction.
* @param organizationID ID of the organization.
* @param newOwnerAddress Owner of the ChugSplashManager contract deployed by this call.
* @returns True if the project was claimed for the first time in this call, and false if the
* project was already claimed by the caller.
* @returns True if the organization ID was already registered for the first time in this call, and
* false if the project was already registered by the caller.
*/
export const claimChugSplashProject = async (
export const finalizeRegistration = async (
provider: providers.JsonRpcProvider,
claimer: Signer,
organizationID: string,
Expand All @@ -244,7 +244,7 @@ export const claimChugSplashProject = async (
)

await (
await ChugSplashRegistry.claim(
await ChugSplashRegistry.finalizeRegistration(
organizationID,
newOwnerAddress,
Object.values(CURRENT_CHUGSPLASH_MANAGER_VERSION),
Expand Down
Loading

0 comments on commit e5b9f81

Please sign in to comment.