Skip to content

Commit

Permalink
TLUniversalDeployer coded & tested
Browse files Browse the repository at this point in the history
  • Loading branch information
mpeyfuss committed Jan 8, 2024
1 parent 0888d0f commit d03d852
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 58 deletions.
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ optimizer = true
optimizer_runs = 20000
verbosity = 3
wrap_comments = true
gas_reports = [""]
gas_reports = ["TLUniversalDeployer"]
fs_permissions = [{ access = "read", path = "./"}]

[fuzz]
Expand Down
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 155d54
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at 01ef44
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
openzeppelin/=lib/openzeppelin-contracts/contracts
openzeppelin/=lib/openzeppelin-contracts/contracts
openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts
102 changes: 48 additions & 54 deletions src/TLUniversalDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {Clones} from "openzeppelin/proxy/Clones.sol";
/// @author transientlabs.xyz
/// @custom:version 1.0.0
contract TLUniversalDeployer is Ownable {

/*//////////////////////////////////////////////////////////////////////////
Custom Types
//////////////////////////////////////////////////////////////////////////*/
Expand All @@ -25,11 +24,11 @@ contract TLUniversalDeployer is Ownable {

/// @dev Struct defining a contract that is deployable
/// @param created A boolean spcifying if the cloneable contract struct has been created or not
/// @param type_ The contract type (human readable - ex: ERC721TL)
/// @param cType The contract type (human readable - ex: ERC721TL)
/// @param versions An array of `ContractVersion` structs that are deployable
struct DeployableContract {
bool created;
string type_;
string cType;
ContractVersion[] versions;
}

Expand All @@ -49,9 +48,15 @@ contract TLUniversalDeployer is Ownable {
/// @param sender The msg sender
/// @param deployedContract The address of the deployed contract
/// @param implementation The address of the implementation contract
/// @param type_ The type of contract deployed
/// @param cType The type of contract deployed
/// @param version The version of contract deployed
event ContractDeployed(address indexed sender, address indexed deployedContract, address indexed implementation, string type_, string version);
event ContractDeployed(
address indexed sender,
address indexed deployedContract,
address indexed implementation,
string cType,
string version
);

/*//////////////////////////////////////////////////////////////////////////
Custom Errors
Expand Down Expand Up @@ -87,7 +92,6 @@ contract TLUniversalDeployer is Ownable {

// verify contract is valid
if (!dc.created) revert InvalidDeployableContract();
if (dc.versions.length == 0) revert InvalidDeployableContract();

// get latest version
ContractVersion memory cv = dc.versions[dc.versions.length - 1];
Expand All @@ -107,7 +111,6 @@ contract TLUniversalDeployer is Ownable {

// verify cloneable contract is valid
if (!dc.created) revert InvalidDeployableContract();
if (dc.versions.length == 0) revert InvalidDeployableContract();
if (versionIndex >= dc.versions.length) revert InvalidDeployableContract();

// get latest version
Expand All @@ -121,35 +124,24 @@ contract TLUniversalDeployer is Ownable {
Admin Functions
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to add a contract type
/// @notice Function to add a contract type and/or version
/// @dev Restricted to only owner
function addDeployableContract(string calldata type_, ContractVersion calldata version) external onlyOwner {
/// @param contractType The contract type to save this under
/// @param version The version to push to the DeployableContract struct
function addDeployableContract(string calldata contractType, ContractVersion calldata version) external onlyOwner {
// get DeployableContract
bytes32 dcId = keccak256(bytes(type_));
DeployableContract storage dc = _deployableContracts[dcId];

// check to see if it's been created or not
if (dc.created) revert ContractAlreadyCreated();

// save to storage
dc.created = true;
dc.type_ = type_;
dc.versions.push(version);
_deployableContractKeys.push(dcId);
}

/// @notice Function to add a contract version
/// @dev Restricted to only owner
/// @dev Does not check if the version exists or not
function addContractVersion(string calldata type_, ContractVersion calldata version) external onlyOwner {
// get DeployableContract
bytes32 dcId = keccak256(bytes(type_));
bytes32 dcId = keccak256(bytes(contractType));
DeployableContract storage dc = _deployableContracts[dcId];

// verify contract is valid
if (!dc.created) revert InvalidDeployableContract();
// if the contract type has not been created, create. e
// else, skip and just push version.
if (!dc.created) {
dc.created = true;
dc.cType = contractType;
_deployableContractKeys.push(dcId);
}

// add version
// push version
dc.versions.push(version);
}

Expand All @@ -158,10 +150,10 @@ contract TLUniversalDeployer is Ownable {
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to get contracts that can be deployed
function getDeployableContracts() external view returns (DeployableContract[] memory) {
DeployableContract[] memory dcs = new DeployableContract[](_deployableContractKeys.length);
function getDeployableContracts() external view returns (string[] memory) {
string[] memory dcs = new string[](_deployableContractKeys.length);
for (uint256 i = 0; i < _deployableContractKeys.length; i++) {
dcs[i] = _deployableContracts[_deployableContractKeys[i]];
dcs[i] = _deployableContracts[_deployableContractKeys[i]].cType;
}

return dcs;
Expand All @@ -179,53 +171,56 @@ contract TLUniversalDeployer is Ownable {

/// @notice Function to predict the address at which a contract would be deployed
/// @dev Predicts for the latest implementation
/// @param sender The sender of the contract deployment transaction
/// @param contractType The contract type to deploy
/// @param initializationCode The initialization code to call after contract deployment
function predictDeployedContractAddress(string calldata contractType, bytes calldata initializationCode) external view returns (address) {
function predictDeployedContractAddress(
address sender,
string calldata contractType,
bytes calldata initializationCode
) external view returns (address) {
// get DeployableContract
bytes32 dcId = keccak256(bytes(contractType));
DeployableContract memory dc = _deployableContracts[dcId];

// verify contract is valid
if (!dc.created) revert InvalidDeployableContract();
if (dc.versions.length == 0) revert InvalidDeployableContract();

// get latest version
ContractVersion memory cv = dc.versions[dc.versions.length - 1];

// create salt by hashing the sender and init code
bytes32 salt = keccak256(abi.encodePacked(
msg.sender,
initializationCode
));
bytes32 salt = keccak256(abi.encodePacked(sender, initializationCode));

// predict
return Clones.predictDeterministicAddress(cv.implementation, salt);
}

/// @notice Function to predict the address at which a contract would be deployed
/// @dev Predicts for a specific implementation
/// @param sender The sender of the contract deployment transaction
/// @param contractType The contract type to deploy
/// @param initializationCode The initialization code to call after contract deployment
/// @param versionIndex The indeex of the `ContractVersion` to deploy
function predictDeployedContractAddress(string calldata contractType, bytes calldata initializationCode, uint256 versionIndex) external view returns (address) {
function predictDeployedContractAddress(
address sender,
string calldata contractType,
bytes calldata initializationCode,
uint256 versionIndex
) external view returns (address) {
// get DeployableContract
bytes32 dcId = keccak256(bytes(contractType));
DeployableContract memory dc = _deployableContracts[dcId];

// verify contract is valid
if (!dc.created) revert InvalidDeployableContract();
if (dc.versions.length == 0) revert InvalidDeployableContract();
if (versionIndex >= dc.versions.length) revert InvalidDeployableContract();

// get latest version
ContractVersion memory cv = dc.versions[versionIndex];

// create salt by hashing the sender and init code
bytes32 salt = keccak256(abi.encodePacked(
msg.sender,
initializationCode
));
bytes32 salt = keccak256(abi.encodePacked(sender, initializationCode));

// predict
return Clones.predictDeterministicAddress(cv.implementation, salt);
Expand All @@ -236,21 +231,20 @@ contract TLUniversalDeployer is Ownable {
//////////////////////////////////////////////////////////////////////////*/

/// @notice Private function to deploy contracts
function _deploy(DeployableContract memory dc, ContractVersion memory cv, bytes memory initializationCode) private {
function _deploy(DeployableContract memory dc, ContractVersion memory cv, bytes memory initializationCode)
private
{
// create salt by hashing the sender and init code
bytes32 salt = keccak256(abi.encodePacked(
msg.sender,
initializationCode
));
bytes32 salt = keccak256(abi.encodePacked(msg.sender, initializationCode));

// clone
address deployedContract = Clones.cloneDeterministic(cv.implementation, salt);

// initialize
(bool success, ) = deployedContract.call(initializationCode);
(bool success,) = deployedContract.call(initializationCode);
if (!success) revert InitializationFailed();

// emit event
emit ContractDeployed(msg.sender, deployedContract, cv.implementation, dc.type_, cv.id);
emit ContractDeployed(msg.sender, deployedContract, cv.implementation, dc.cType, cv.id);
}
}
}
Loading

0 comments on commit d03d852

Please sign in to comment.