Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UniversalFactory #139

Merged
merged 23 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8b96f2b
feat: add UniversalFactory contract
YamenMerhi Apr 15, 2022
2b5c196
test: add UniversalFactory tests
YamenMerhi Apr 15, 2022
768ce28
build: add UniversalFactory contract to `hardhat.config.ts`
YamenMerhi Apr 15, 2022
a9d8939
refactor: add `_calculateSalt` internal function
YamenMerhi Apr 21, 2022
c55f126
fix: fix naming on parameters
YamenMerhi Apr 26, 2022
8cbe0e7
chore: code refactor
frozeman Apr 26, 2022
96236c1
chore: refactor the code of the UniversalFactory
YamenMerhi Apr 27, 2022
f5be547
test: fix the failing tests
YamenMerhi Apr 27, 2022
2bed34f
Merge branch 'develop' of https://github.com/lukso-network/lsp-smart-…
YamenMerhi Apr 27, 2022
b8a5344
build: add command to test UniversalFactory
YamenMerhi Apr 27, 2022
dacd9d5
ci: add parallel test of UniversalFactory
YamenMerhi Apr 27, 2022
a0938e9
test: import provider from helpers
YamenMerhi Apr 27, 2022
5a253ba
test: fix var naming and typos
YamenMerhi Apr 27, 2022
c6052c1
test: refer to empty bytes with "0x" instead of []
YamenMerhi Apr 27, 2022
3b74798
chore: fix linter warnings
YamenMerhi Apr 27, 2022
e2b1e20
fix: resolve merge conflicts in LSP1
YamenMerhi May 17, 2022
3e249a4
fix: remove value sent when initializing base contracts
YamenMerhi May 17, 2022
bf9aa72
test: add helpers constants
YamenMerhi May 17, 2022
022c51f
chore: add helpers contract to test UniversalFactory
YamenMerhi May 17, 2022
0741694
chore: improve UniversalFactory contract nastpec
YamenMerhi May 17, 2022
3550a22
test: add more teststo UniversalFactory contract
YamenMerhi May 17, 2022
49f3097
ci: resolve merge conflicts in the ci
YamenMerhi May 20, 2022
7056909
ci: edit factory test command in `package.json`
YamenMerhi May 20, 2022
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
32 changes: 32 additions & 0 deletions .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,38 @@ jobs:
- name: run LSP9 tests
run: npm run test:lsp9

test-UniversalFactory:
YamenMerhi marked this conversation as resolved.
Show resolved Hide resolved
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v2

- name: restore cache
uses: actions/cache@v2
with:
path: |
artifacts
node_modules
types
key: ${{ github.run_id }}

- name: Use Node.js v16
uses: actions/setup-node@v2
with:
node-version: "16.x"
cache: "npm"

- name: Install dependencies
if: steps.build-cache.outputs.cache-hit != 'true'
run: npm ci

- name: Generate typechain types
if: steps.build-cache.outputs.cache-hit != 'true'
run: npm run generate-types

- name: run UniversalFactory tests
run: npm run test:factory

test-Helpers:
runs-on: ubuntu-latest
needs: [build]
Expand Down
154 changes: 154 additions & 0 deletions contracts/Factories/UniversalFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// libraries
import "@erc725/smart-contracts/contracts/utils/ErrorHandlerLib.sol";
import "@openzeppelin/contracts/utils/Create2.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";

/**
* @dev UniversalFactory contract can be used to deploy create2 contracts; normal contracts and minimal
* proxies (EIP-1167) with the ability to deploy the same contract at the same address on different chains.
* If the contract has a constructor, the arguments will be part of the byteCode
* If the contract has an `initialize` function, we need to include the parameters of this function in
* the salt to ensure that the parameters of the contract should be the same on each chain.
*
* Security measures were taken to avoid deploying proxies from the `deployCreate2(..)` function
* to prevent the problem mentioned above.
*
* This contract should be deployed using Nick's Method.
YamenMerhi marked this conversation as resolved.
Show resolved Hide resolved
* More information: https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7
*/
contract UniversalFactory {
using BytesLib for bytes;

// The bytecode hash of EIP-1167 Minimal Proxy
bytes32 private constant _MINIMAL_PROXY_BYTECODE_HASH_PT1 =
0x72307939328b75c6e301a012c75e0a4e690a99036b95f6e6f4f1b5aba02a9ce4;

bytes32 private constant _MINIMAL_PROXY_BYTECODE_HASH_PT2 =
0x11a195f66c9175f46895bae2006d40848a680c7068b9fc4af248ff9a54a47e45;

/**
* @dev Throws if the `byteCode` passed to the function is the EIP-1167 Minimal Proxy bytecode
*/
modifier notMinimalProxy(bytes memory byteCode) virtual {
if (byteCode.length == 55) {
if (
keccak256(byteCode.slice(0, 20)) == _MINIMAL_PROXY_BYTECODE_HASH_PT1 &&
keccak256(byteCode.slice(40, 15)) == _MINIMAL_PROXY_BYTECODE_HASH_PT2
) {
revert("Minimal Proxies deployment not allowed");
}
}
_;
}

/**
* @dev Returns the address where a contract will be stored if deployed via `CREATE2`. The address is
* constructed using the parameters below. Any change in one of them will result in a new destination address.
*/
function calculateAddress(
bytes32 byteCodeHash,
bytes32 salt,
bytes memory initializeCallData
) public view returns (address) {
bytes32 generatedSalt = _generateSalt(initializeCallData, salt);
return Create2.computeAddress(generatedSalt, byteCodeHash);
}

/**
* @dev Returns the address of an EIP1167 proxy contract. The address is constructed using
* the parameters below. Any change in one of them will result in a new destination address.
*/
function calculateProxyAddress(
address baseContract,
bytes32 salt,
bytes memory initializeCallData
) public view returns (address) {
bytes32 generatedSalt = _generateSalt(initializeCallData, salt);
return Clones.predictDeterministicAddress(baseContract, generatedSalt);
}

/**
* @dev Deploys a contract using `CREATE2`. The address where the contract will be deployed
* can be known in advance via {calculateAddress}. The salt is a combination between the `initializable`,
* `salt` and the `initializeCallData` if the contract is initializable. This method allow users
* to have the same contracts at the same address across different chains with the same parameters.
*
* Using the same `byteCode` and salt multiple time will revert, since
* the contract cannot be deployed twice at the same address.
*
* Deploying a minimal proxy from this function will revert.
*/
function deployCreate2(
bytes memory byteCode,
bytes32 salt,
bytes memory initializeCallData
) public payable notMinimalProxy(byteCode) returns (address contractCreated) {
bytes32 generatedSalt = _generateSalt(initializeCallData, salt);
contractCreated = Create2.deploy(msg.value, generatedSalt, byteCode);

if (initializeCallData.length > 0) {
(bool success, bytes memory returnedData) = contractCreated.call{value: msg.value}(
initializeCallData
);
if (!success) ErrorHandlerLib.revertWithParsedError(returnedData);
}
}

/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `baseContract`.
* The address where the contract will be deployed can be known in advance via {calculateProxyAddress}.
*
* This function uses the create2 opcode and a salt to deterministically deploy
* the clone. The salt is a combination between the `initializable`, `salt`
* and the `initializeCallData` if the contract is initializable. This method allow users
* to have the same contracts at the same address across different chains with the same parameters.
*
* Using the same `baseContract` and salt multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function deployCreate2Proxy(
address baseContract,
bytes32 salt,
bytes memory initializeCallData
) public payable returns (address proxy) {
bytes32 generatedSalt = _generateSalt(initializeCallData, salt);
proxy = Clones.cloneDeterministic(baseContract, generatedSalt);

if (initializeCallData.length > 0) {
(bool success, bytes memory returnedData) = proxy.call{value: msg.value}(
initializeCallData
);
if (!success) ErrorHandlerLib.revertWithParsedError(returnedData);
} else {
// Return value sent
if (msg.value > 0) {
(bool success, bytes memory returnedData) = payable(msg.sender).call{
value: msg.value
}("");
if (!success) ErrorHandlerLib.revertWithParsedError(returnedData);
}
}
}

/** internal functions */

/**
* @dev Calculates the salt including the initializeCall data, or without but hashing it with a zero bytes padding.
*/
function _generateSalt(bytes memory initializeCallData, bytes32 salt)
internal
pure
returns (bytes32)
{
bool initializable = initializeCallData.length > 0;
if (initializable) {
return keccak256(abi.encodePacked(initializable, initializeCallData, salt));
} else {
return keccak256(abi.encodePacked(initializable, salt));
}
}
}
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const config: HardhatUserConfig = {
// Tools
// ------------------
"Create2Factory",
"UniversalFactory"
],
// Whether to include the TypeChain factories or not.
// If this is enabled, you need to run the TypeChain files through the TypeScript compiler before shipping to the registry.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
],
"scripts": {
"test": "npx hardhat compile; NODE_NO_WARNINGS=1 jest",
"test:parallel": "run-p test:up test:lsp1 test:lsp4 test:lsp6 test:lsp7 test:lsp8 test:lsp9",
"test:parallel": "run-p test:up test:lsp1 test:lsp4 test:lsp6 test:lsp7 test:lsp8 test:lsp9 test:factory",
"test:helpers": "NODE_NO_WARNINGS=1 jest tests/Helpers/*.test.ts",
"test:up": "NODE_NO_WARNINGS=1 jest UniversalProfile",
"test:lsp1": "NODE_NO_WARNINGS=1 jest LSP1",
Expand All @@ -25,6 +25,7 @@
"test:lsp7": "NODE_NO_WARNINGS=1 jest LSP7",
"test:lsp8": "NODE_NO_WARNINGS=1 jest LSP8",
"test:lsp9": "NODE_NO_WARNINGS=1 jest LSP9",
"test:factory": "NODE_NO_WARNINGS=1 jest UniversalFactory",
"build": "npx hardhat compile",
"package": "npx hardhat prepare-package",
"release": "run-s clean build package && npx standard-version",
Expand Down
Loading