mirror of
https://github.com/luxfi/erc-3643.git
synced 2026-10-30 16:58:28 +00:00
* ✨ integrate foundry alongside hardhat for dual testing framework - Add foundry.toml configuration - Reorganize test structure: test/forge/ and test/hardhat/ - Add Foundry scripts to package.json (build:forge, test:forge, coverage:forge) - Update hardhat.config.ts with evmVersion for compatibility - Add GitHub workflows and coverage scripts from template - Add Husky hooks (pre-commit, post-checkout, post-merge) - Add .vscode/settings.json for better IDE support - Disable coverage checks until tests are migrated (commented with TODO) This migration is based on TokenySolutions/contracts-template and enables gradual migration of tests from Hardhat (TypeScript) to Foundry (Solidity). Related to: BT-1418 * ✅ trex factory tests * ⚰️ remove trex suit * feat(forge): reorganize Foundry test structure and sync GitHub workflows - Move TREXFactorySetup to helpers folder - Rename TREXSuite.t.sol to TREXFactory.t.sol - Update fixture to inherit from Test for proper Foundry pattern - Transfer ownership to deployer address to match Hardhat behavior - Sync GitHub workflows with package.json scripts - Update test.yml to use npm run build and npm run test - Update push_checking.yml to use unified npm run lint - Update forge_coverage.sh to use npm run coverage * fix(ci): install Foundry and skip husky in push_checking workflow - Add Foundry installation step before npm ci - Set HUSKY=0 to skip husky setup in CI environment - Fixes 'forge: not found' error during npm prepare script * 🔧 keep hardhat settings * ✅ create addressess directly * ✅ removed test_StandardAddressesSet * ✅ fix lint * 💡 removed title natspecs * ✅ migrated trex factort tests * ✅ added test module multicall * ✅ migrate agent role test * ✅ migrate claim topics registry * ✅ migrated trusted issuer registry * ✅ migrate identity registry storage * ✅ migrated identity registry tests * ✅ migrated implementation authority test * ✅ migrated token agen restrictions * ✅ migrated token information test * ✅ migrated token recover * ✅ migrated token permit * ✅ migrated token transfer * ✅ migrated utilities upgrade * ✅ migrated freeze check * ✅ migrated compliance check in utilities test * ✅ migrated eligibility check * ✅ migrated transfer check in utilities * ✅ migrated compliance test * ✅ migrated gateway tests * 🔥 removed already defined events * 🗑️ imported Initializable error from openzeppelin * ➕ added already defined events and errors * ✅ added base test token * ✅ used idFactory * ♻️ refactored compliance test * ⚰️ remoced useless vm.prank * 🔧 deleted hardhat configurations * 💚 fixed ci * refactor: remove 100% coverage requirement from forge_coverage.sh - Remove strict 100% coverage checks that were causing workflow failures - Script now only generates and displays coverage report without failing - Aligns with previous behavior where coverage was informational only * ✅ resolve getVerifiedDetails * ✅ applied _getEligibility and remove via-ir from foundry.toml * ✅ cover tokena and token permit * ✅ cover compliance * ✅ cover identity registry * ✅ increased trex gateway coverage * ✅ increase coverage for trex factory * ✅ increased AbstractModuleUpgradeable.sol coverage * ⚰️ removed abstract module * 🔧 updted generate coverage * ✅ coverage 100% identity registry * ✅ coverage 100% trex gate way * ✅ coverage trex factory * ✅ coverage 100% AbstractModuleUpgradeable by test module * ✅ coverage IAFactory.sol * ✅ coverage 100% for implementation authority * ✅ coverage 1005 abstract proxy * ✅ coverage 100% claim topic registry * ✅ coverage 100% identity registry storage * ✅ coverage 100% modular compliance proxy * ✅ coverage trusted issuer registry proxy * ✅ coverage 100% identity registry * ✅ coverage 100% token proxy * 🚨 resolve linter in token information * ✅ coverage 100% roles * 🚨 resolve linter * 🔧 added no coverage for test, _testContracts and utils * ✅ coverage 100% for token.sol * ✅ coverage 100% for modular compliance * ✅ coverage 100% trusted issuer registry * 🔧 restore package.json * ✅ coverage 100% for utlity checker * 🚨 fix linter * 💥 added create 3 from createx library * 🙈 added dependencies folder and out to gitignore * 🚨 forge fmt linting * 💥 duplicated createX * refactor: organize CreateX files into utils/createx subfolder * 🚨 fix linting * 💥 added suffix contract type salt * ✅ added createX computeCreate3Address tests * 💥 updated saltBytes * ✅ tested multiple chains * 💥 immutable create3 factory variable * 💥 updated create3 sources * 💥 added fork links * ⚰️ removed unused code from createx * 🔧 added createx dependency * 💥 resolve dependency * 💥 added "@" to dependency * 💥 added deployment on different chains * 💥 added vm.createSelectFork * 🔧 Fix config for CI * 💚 Fix CI workflows --------- Co-authored-by: Philippe Gonday <[email protected]>
297 lines
17 KiB
Solidity
297 lines
17 KiB
Solidity
// SPDX-License-Identifier: AGPL-3.0-only
|
||
pragma solidity 0.8.30;
|
||
|
||
/**
|
||
* @title CreateX Factory Smart Contract (Minimal Version)
|
||
* @notice Factory smart contract for CREATE3-based contract creations.
|
||
* @dev This is a minimal version containing only the methods used by TREXFactory and tests.
|
||
*/
|
||
contract CreateX {
|
||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
||
/* IMMUTABLES */
|
||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
||
|
||
/**
|
||
* @dev Caches the contract address at construction, to be used for the custom errors.
|
||
*/
|
||
address internal immutable _SELF = address(this);
|
||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
||
/* TYPES */
|
||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
||
|
||
struct Values {
|
||
uint256 constructorAmount;
|
||
uint256 initCallAmount;
|
||
}
|
||
|
||
enum SenderBytes {
|
||
MsgSender,
|
||
ZeroAddress,
|
||
Random
|
||
}
|
||
|
||
enum RedeployProtectionFlag {
|
||
True,
|
||
False,
|
||
Unspecified
|
||
}
|
||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
||
/* EVENTS */
|
||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
||
|
||
event ContractCreation(address indexed newContract);
|
||
event Create3ProxyContractCreation(address indexed newContract, bytes32 indexed salt);
|
||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
||
/* CUSTOM ERRORS */
|
||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
||
|
||
error FailedContractCreation(address emitter);
|
||
error FailedContractInitialisation(address emitter, bytes revertData);
|
||
error InvalidSalt(address emitter);
|
||
error FailedEtherTransfer(address emitter, bytes revertData);
|
||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
||
/* CREATE3 */
|
||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
||
|
||
/**
|
||
* @dev Deploys and initialises a new contract via employing the `CREATE3` pattern (i.e. without an initcode
|
||
* factor) and using the salt value `salt`, the creation bytecode `initCode`, the initialisation code `data`,
|
||
* the struct for the `payable` amounts `values`, the refund address `refundAddress`, and `msg.value` as inputs.
|
||
* In order to save deployment costs, we do not sanity check the `initCode` length. Note that if
|
||
* `values.constructorAmount` is non-zero, `initCode` must have a `payable` constructor. This implementation
|
||
* is based on Solmate:
|
||
* https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol.
|
||
* @param salt The 32-byte random value used to create the proxy contract address.
|
||
* @param initCode The creation bytecode.
|
||
* @param data The initialisation code that is passed to the deployed contract.
|
||
* @param values The specific `payable` amounts for the deployment and initialisation call.
|
||
* @param refundAddress The 20-byte address where any excess ether is returned to.
|
||
* @return newContract The 20-byte address where the contract was deployed.
|
||
* @custom:security This function allows for reentrancy, however we refrain from adding
|
||
* a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol
|
||
* level that potentially malicious reentrant calls do not affect your smart contract system.
|
||
* Furthermore, we strongly recommend implementing a permissioned deploy protection by setting
|
||
* the first 20 bytes equal to `msg.sender` in the `salt` to prevent maliciously intended frontrun
|
||
* proxy deployments on other chains.
|
||
*/
|
||
function deployCreate3AndInit(
|
||
bytes32 salt,
|
||
bytes memory initCode,
|
||
bytes memory data,
|
||
Values memory values,
|
||
address refundAddress
|
||
) public payable returns (address newContract) {
|
||
bytes32 guardedSalt = _guard({ salt: salt });
|
||
bytes memory proxyChildBytecode = hex"67363d3d37363d34f03d5260086018f3";
|
||
address proxy;
|
||
assembly ("memory-safe") {
|
||
proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), guardedSalt)
|
||
}
|
||
if (proxy == address(0)) {
|
||
revert FailedContractCreation({ emitter: _SELF });
|
||
}
|
||
emit Create3ProxyContractCreation({ newContract: proxy, salt: guardedSalt });
|
||
|
||
newContract = computeCreate3Address({ salt: guardedSalt });
|
||
(bool success,) = proxy.call{ value: values.constructorAmount }(initCode);
|
||
_requireSuccessfulContractCreation({ success: success, newContract: newContract });
|
||
emit ContractCreation({ newContract: newContract });
|
||
|
||
bytes memory returnData;
|
||
(success, returnData) = newContract.call{ value: values.initCallAmount }(data);
|
||
if (!success) {
|
||
revert FailedContractInitialisation({ emitter: _SELF, revertData: returnData });
|
||
}
|
||
|
||
if (_SELF.balance != 0) {
|
||
// Any wei amount previously forced into this contract (e.g. by using the `SELFDESTRUCT`
|
||
// opcode) will be part of the refund transaction.
|
||
(success, returnData) = refundAddress.call{ value: _SELF.balance }("");
|
||
if (!success) {
|
||
revert FailedEtherTransfer({ emitter: _SELF, revertData: returnData });
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @dev Deploys and initialises a new contract via employing the `CREATE3` pattern (i.e. without
|
||
* an initcode factor) and using the salt value `salt`, the creation bytecode `initCode`, the
|
||
* initialisation code `data`, the struct for the `payable` amounts `values`, and `msg.value` as
|
||
* inputs. In order to save deployment costs, we do not sanity check the `initCode` length. Note
|
||
* that if `values.constructorAmount` is non-zero, `initCode` must have a `payable` constructor,
|
||
* and any excess ether is returned to `msg.sender`. This implementation is based on Solmate:
|
||
* https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol.
|
||
* @param salt The 32-byte random value used to create the proxy contract address.
|
||
* @param initCode The creation bytecode.
|
||
* @param data The initialisation code that is passed to the deployed contract.
|
||
* @param values The specific `payable` amounts for the deployment and initialisation call.
|
||
* @return newContract The 20-byte address where the contract was deployed.
|
||
* @custom:security This function allows for reentrancy, however we refrain from adding
|
||
* a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol
|
||
* level that potentially malicious reentrant calls do not affect your smart contract system.
|
||
* Furthermore, we strongly recommend implementing a permissioned deploy protection by setting
|
||
* the first 20 bytes equal to `msg.sender` in the `salt` to prevent maliciously intended frontrun
|
||
* proxy deployments on other chains.
|
||
*/
|
||
function deployCreate3AndInit(bytes32 salt, bytes memory initCode, bytes memory data, Values memory values)
|
||
public
|
||
payable
|
||
returns (address newContract)
|
||
{
|
||
// Note that the safeguarding function `_guard` is called as part of the overloaded function
|
||
// `deployCreate3AndInit`.
|
||
newContract = deployCreate3AndInit({
|
||
salt: salt, initCode: initCode, data: data, values: values, refundAddress: msg.sender
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the address where a contract will be stored if deployed via `deployer` using
|
||
* the `CREATE3` pattern (i.e. without an initcode factor). Any change in the `salt` value will
|
||
* result in a new destination address. This implementation is based on Solady:
|
||
* https://web.archive.org/web/20230921114120/https://raw.githubusercontent.com/Vectorized/solady/1c1ac4ad9c8558001e92d8d1a7722ef67bec75df/src/utils/CREATE3.sol.
|
||
* @param salt The 32-byte random value used to create the proxy contract address.
|
||
* @param deployer The 20-byte deployer address.
|
||
* @return computedAddress The 20-byte address where a contract will be stored.
|
||
*/
|
||
function computeCreate3Address(bytes32 salt, address deployer) public pure returns (address computedAddress) {
|
||
assembly ("memory-safe") {
|
||
let ptr := mload(0x40)
|
||
mstore(0x00, deployer)
|
||
mstore8(0x0b, 0xff)
|
||
mstore(0x20, salt)
|
||
mstore(0x40, hex"21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f")
|
||
mstore(0x14, keccak256(0x0b, 0x55))
|
||
mstore(0x40, ptr)
|
||
mstore(0x00, 0xd694)
|
||
mstore8(0x34, 0x01)
|
||
computedAddress := keccak256(0x1e, 0x17)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the address where a contract will be stored if deployed via this contract using CREATE3.
|
||
* @param salt The 32-byte random value used to create the proxy contract address.
|
||
* @return computedAddress The 20-byte address where a contract will be stored.
|
||
*/
|
||
function computeCreate3Address(bytes32 salt) public view returns (address computedAddress) {
|
||
computedAddress = computeCreate3Address({ salt: salt, deployer: _SELF });
|
||
}
|
||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
||
/* HELPER FUNCTIONS */
|
||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
||
|
||
/**
|
||
* @dev Implements different safeguarding mechanisms depending on the encoded values in the salt
|
||
* (`||` stands for byte-wise concatenation):
|
||
* => salt (32 bytes) = 0xbebebebebebebebebebebebebebebebebebebebe||ff||1212121212121212121212
|
||
* - The first 20 bytes (i.e. `bebebebebebebebebebebebebebebebebebebebe`) may be used to
|
||
* implement a permissioned deploy protection by setting them equal to `msg.sender`,
|
||
* - The 21st byte (i.e. `ff`) may be used to implement a cross-chain redeploy protection by
|
||
* setting it equal to `0x01`,
|
||
* - The last random 11 bytes (i.e. `1212121212121212121212`) allow for 2**88 bits of entropy
|
||
* for mining a salt.
|
||
* @param salt The 32-byte random value used to create the contract address.
|
||
* @return guardedSalt The guarded 32-byte random value used to create the contract address.
|
||
*/
|
||
function _guard(bytes32 salt) internal view returns (bytes32 guardedSalt) {
|
||
(SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) = _parseSalt({ salt: salt });
|
||
|
||
if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.True) {
|
||
// Configures a permissioned deploy protection as well as a cross-chain redeploy protection.
|
||
guardedSalt = keccak256(abi.encode(msg.sender, block.chainid, salt));
|
||
} else if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.False) {
|
||
// Configures solely a permissioned deploy protection.
|
||
guardedSalt = _efficientHash({ a: bytes32(uint256(uint160(msg.sender))), b: salt });
|
||
} else if (senderBytes == SenderBytes.MsgSender) {
|
||
// Reverts if the 21st byte is greater than `0x01` in order to enforce developer explicitness.
|
||
revert InvalidSalt({ emitter: _SELF });
|
||
} else if (senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.True) {
|
||
// Configures solely a cross-chain redeploy protection. In order to prevent a pseudo-randomly
|
||
// generated cross-chain redeploy protection, we enforce the zero address check for the first 20 bytes.
|
||
guardedSalt = _efficientHash({ a: bytes32(block.chainid), b: salt });
|
||
} else if (
|
||
senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.Unspecified
|
||
) {
|
||
// Reverts if the 21st byte is greater than `0x01` in order to enforce developer explicitness.
|
||
revert InvalidSalt({ emitter: _SELF });
|
||
} else {
|
||
// For the non-pseudo-random cases, the salt value `salt` is hashed to prevent the safeguard mechanisms
|
||
// from being bypassed. Otherwise, the salt value `salt` is not modified.
|
||
guardedSalt = keccak256(abi.encode(salt));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the enum for the selection of a permissioned deploy protection as well as a
|
||
* cross-chain redeploy protection.
|
||
* @param salt The 32-byte random value used to create the contract address.
|
||
* @return senderBytes The 8-byte enum for the selection of a permissioned deploy protection.
|
||
* @return redeployProtectionFlag The 8-byte enum for the selection of a cross-chain redeploy
|
||
* protection.
|
||
*/
|
||
function _parseSalt(bytes32 salt)
|
||
internal
|
||
view
|
||
returns (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag)
|
||
{
|
||
if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"01") {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.True);
|
||
} else if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"00") {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.False);
|
||
} else if (address(bytes20(salt)) == msg.sender) {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.Unspecified);
|
||
} else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"01") {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.True);
|
||
} else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"00") {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.False);
|
||
} else if (address(bytes20(salt)) == address(0)) {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.Unspecified);
|
||
} else if (bytes1(salt[20]) == hex"01") {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.True);
|
||
} else if (bytes1(salt[20]) == hex"00") {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.False);
|
||
} else {
|
||
(senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.Unspecified);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the `keccak256` hash of `a` and `b` after concatenation.
|
||
* @param a The first 32-byte value to be concatenated and hashed.
|
||
* @param b The second 32-byte value to be concatenated and hashed.
|
||
* @return hash The 32-byte `keccak256` hash of `a` and `b`.
|
||
*/
|
||
function _efficientHash(bytes32 a, bytes32 b) internal pure returns (bytes32 hash) {
|
||
assembly ("memory-safe") {
|
||
mstore(0x00, a)
|
||
mstore(0x20, b)
|
||
hash := keccak256(0x00, 0x40)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @dev Ensures that `newContract` is a non-zero byte contract.
|
||
* @param success The Boolean success condition.
|
||
* @param newContract The 20-byte address where the contract was deployed.
|
||
*/
|
||
function _requireSuccessfulContractCreation(bool success, address newContract) internal view {
|
||
// Note that reverting if `newContract == address(0)` isn't strictly necessary here, as if
|
||
// the deployment fails, `success == false` should already hold. However, since the `CreateX`
|
||
// contract should be usable and safe on a wide range of chains, this check is cheap enough
|
||
// that there is no harm in including it (security > gas optimisations). It can potentially
|
||
// protect against unexpected chain behaviour or a hypothetical compiler bug that doesn't surface
|
||
// the call success status properly.
|
||
if (!success || newContract == address(0) || newContract.code.length == 0) {
|
||
revert FailedContractCreation({ emitter: _SELF });
|
||
}
|
||
}
|
||
|
||
}
|