diff --git a/contracts/factory/ContractsDeployer.sol b/contracts/factory/ContractsDeployer.sol
new file mode 100644
index 00000000..9724cd3b
--- /dev/null
+++ b/contracts/factory/ContractsDeployer.sol
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-3.0
+//
+// :+#####%%%%%%%%%%%%%%+
+// .-*@@@%+.:+%@@@@@%%#***%@@%=
+// :=*%@@@#=. :#@@% *@@@%=
+// .-+*%@%*-.:+%@@@@@@+. -*+: .=#. :%@@@%-
+// :=*@@@@%%@@@@@@@@@%@@@- .=#@@@%@%= =@@@@#.
+// -=+#%@@%#*=:. :%@@@@%. -*@@#*@@@@@@@#=:- *@@@@+
+// =@@%=:. :=: *@@@@@%#- =%*%@@@@#+-. =+ :%@@@%-
+// -@@%. .+@@@ =+=-. @@#- +@@@%- =@@@@%:
+// :@@@. .+@@#%: : .=*=-::.-%@@@+*@@= +@@@@#.
+// %@@: +@%%* =%@@@@@@@@@@@#. .*@%- +@@@@*.
+// #@@= .+@@@@%:=*@@@@@- :%@%: .*@@@@+
+// *@@* +@@@#-@@%-:%@@* +@@#. :%@@@@-
+// -@@% .:-=++*##%%%@@@@@@@@@@@@*. :@+.@@@%: .#@@+ =@@@@#:
+// .@@@*-+*#%%%@@@@@@@@@@@@@@@@%%#**@@%@@@. *@=*@@# :#@%= .#@@@@#-
+// -%@@@@@@@@@@@@@@@*+==-:-@@@= *@# .#@*-=*@@@@%= -%@@@* =@@@@@%-
+// -+%@@@#. %@%%= -@@:+@: -@@* *@@*-:: -%@@%=. .*@@@@@#
+// *@@@* +@* *@@##@@- #@*@@+ -@@= . :+@@@#: .-+@@@%+-
+// +@@@%*@@:..=@@@@* .@@@* .#@#. .=+- .=%@@@*. :+#@@@@*=:
+// =@@@@%@@@@@@@@@@@@@@@@@@@@@@%- :+#*. :*@@@%=. .=#@@@@%+:
+// .%@@= ..... .=#@@+. .#@@@*: -*%@@@@%+.
+// +@@#+===---:::... .=%@@*- +@@@+. -*@@@@@%+.
+// -@@@@@@@@@@@@@@@@@@@@@@%@@@@= -@@@+ -#@@@@@#=.
+// ..:::---===+++***###%%%@@@#- .#@@+ -*@@@@@#=.
+// @@@@@@+. +@@*. .+@@@@@%=.
+// -@@@@@= =@@%: -#@@@@%+.
+// +@@@@@. =@@@= .+@@@@@*:
+// #@@@@#:%@@#. :*@@@@#-
+// @@@@@%@@@= :#@@@@+.
+// :@@@@@@@#.:#@@@%-
+// +@@@@@@-.*@@@*:
+// #@@@@#.=@@@+.
+// @@@@+-%@%=
+// :@@@#%@%=
+// +@@@@%-
+// :#%%=
+//
+/**
+ * NOTICE
+ *
+ * The T-REX software is licensed under a proprietary license or the GPL v.3.
+ * If you choose to receive it under the GPL v.3 license, the following applies:
+ * T-REX is a suite of smart contracts implementing the ERC-3643 standard and
+ * developed by Tokeny to manage and transfer financial assets on EVM blockchains
+ *
+ * Copyright (C) 2023, Tokeny sàrl.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+pragma solidity 0.8.17;
+
+import "../roles/AgentRole.sol";
+
+/// @notice Error thrown when a contract with the same name has already been deployed.
+/// @param addr The address of the previously deployed contract.
+error ContractDeployedAlready(address addr);
+
+contract ContractsDeployer is AgentRole {
+
+ /// @notice Maps a human-readable name to the address of a deployed contract.
+ /// @dev Used to retrieve contract addresses deployed by this deployer.
+ mapping(string => address) private _deployedContracts;
+
+ /// @notice Emitted when a contract is deployed.
+ /// @param name The human-readable name of the deployed contract.
+ /// @param contractAddress The address of the deployed contract.
+ event ContractDeployed(string name, address contractAddress);
+
+
+ /**
+ * @dev Deploys a contract using the create2 opcode, ensuring deterministic address generation.
+ * @param name A human-readable name for the contract, used for referencing in the deployedContracts mapping.
+ * @param bytecode The bytecode of the contract to be deployed.
+ * @return addr The address of the deployed contract.
+ * @notice The function will revert with `ContractDeployedAlready` if a contract with the same name has been deployed.
+ */
+ function deployContract(string memory name, bytes memory bytecode) external onlyAgent returns (address) {
+ bytes32 salt = keccak256(bytecode);
+ if (_deployedContracts[name] != address(0)) {
+ revert ContractDeployedAlready(_deployedContracts[name]);
+ }
+
+ address addr;
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ let encoded_data := add(0x20, bytecode) // Load initialization code.
+ let encoded_size := mload(bytecode) // Load init code's length.
+ addr := create2(0, encoded_data, encoded_size, salt)
+ if iszero(extcodesize(addr)) {
+ revert(0, 0)
+ }
+ }
+ _deployedContracts[name] = addr;
+ emit ContractDeployed(name, addr);
+ return addr;
+ }
+
+ /**
+ * @dev Transfers the ownership of a contract to a new owner.
+ * @param _contract The address of the contract whose ownership is to be transferred.
+ * @param _owner The address of the new owner.
+ * @notice This function can only be called by an agent.
+ */
+ function recoverContractOwnership(address _contract, address _owner) external onlyAgent {
+ Ownable(_contract).transferOwnership(_owner);
+ }
+
+ /**
+ * @dev Retrieves the address of a deployed contract by its name.
+ * @param name The name of the contract.
+ * @return The address of the deployed contract.
+ */
+ function getContract(string calldata name) external view returns (address) {
+ return _deployedContracts[name];
+ }
+}
diff --git a/index.d.ts b/index.d.ts
index 65d49032..190fedbf 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -6,7 +6,7 @@ type ContractJSON = {
bytecode: string;
deployedBytecode: string;
linkReferences: any;
-}
+};
export namespace contracts {
// Token
@@ -48,6 +48,9 @@ export namespace contracts {
export const TREXFactory: ContractJSON;
// gateway
export const TREXGateway: ContractJSON;
+ // contractsDeployer
+
+ export const ContractsDeployer: ContractJSON;
// DVD
export const DVDTransferManager: ContractJSON;
// DVA
diff --git a/index.js b/index.js
index 4a2d8819..fac7cd06 100644
--- a/index.js
+++ b/index.js
@@ -48,6 +48,8 @@ const TREXFactory = require('./artifacts/contracts/factory/TREXFactory.sol/TREXF
// gateway
const ITREXGateway = require('./artifacts/contracts/factory/ITREXGateway.sol/ITREXGateway.json');
const TREXGateway = require('./artifacts/contracts/factory/TREXGateway.sol/TREXGateway.json');
+// contractsDeployer
+const ContractsDeployer = require('./artifacts/contracts/factory/ContractsDeployer.sol/ContractsDeployer.json');
// DVD
const DVDTransferManager = require('./artifacts/contracts/DVD/DVDTransferManager.sol/DVDTransferManager.json');
// DVA
@@ -112,6 +114,8 @@ module.exports = {
TREXFactory,
// gateway
TREXGateway,
+ // contractsDeployer
+ ContractsDeployer,
// DVD
DVDTransferManager,
// DVA
diff --git a/package-lock.json b/package-lock.json
index ba8037dd..bd7abd63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,13 +6,13 @@
"packages": {
"": {
"name": "@tokenysolutions/t-rex",
- "version": "4.1.0-beta5",
+ "version": "4.1.1",
"license": "SEE LICENSE IN LICENSE.md",
"devDependencies": {
"@commitlint/cli": "^17.6.1",
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
"@nomiclabs/hardhat-solhint": "^3.0.1",
- "@onchain-id/solidity": "^2.0.0",
+ "@onchain-id/solidity": "^2.2.0",
"@openzeppelin/contracts": "^4.8.3",
"@openzeppelin/contracts-upgradeable": "^4.8.3",
"@primitivefi/hardhat-dodoc": "^0.2.3",
@@ -2429,9 +2429,9 @@
}
},
"node_modules/@onchain-id/solidity": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@onchain-id/solidity/-/solidity-2.1.0.tgz",
- "integrity": "sha512-BBpBmUs8gg99N5NIKzANbs3R6DUhd90z2GEFvScVBhpHvpQhI5IzPNFRP6cXua1Lo0dWdNffxsteB3eUY1XMZw==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@onchain-id/solidity/-/solidity-2.2.0.tgz",
+ "integrity": "sha512-rUE5kFToihuvRAhtZXihOrpsqPOxN66v9hmTzlZOQJ2B+yKruLg4/nU/sJ7VCnheW4IeEm8OlA2oUJsdm1j88w==",
"dev": true
},
"node_modules/@openzeppelin/contracts": {
diff --git a/package.json b/package.json
index 55686019..22aecea2 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
"@commitlint/cli": "^17.6.1",
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
"@nomiclabs/hardhat-solhint": "^3.0.1",
- "@onchain-id/solidity": "^2.0.0",
+ "@onchain-id/solidity": "^2.2.0",
"@openzeppelin/contracts": "^4.8.3",
"@openzeppelin/contracts-upgradeable": "^4.8.3",
"@primitivefi/hardhat-dodoc": "^0.2.3",
diff --git a/test/fixtures/deploy-full-suite.fixture.ts b/test/fixtures/deploy-full-suite.fixture.ts
index eefa68ed..56198973 100644
--- a/test/fixtures/deploy-full-suite.fixture.ts
+++ b/test/fixtures/deploy-full-suite.fixture.ts
@@ -12,40 +12,68 @@ export async function deployIdentityProxy(implementationAuthority: Contract['add
return ethers.getContractAt('Identity', identity.address, signer);
}
+// Function to deploy a single contract through ContractsDeployer
+async function deployAndLoadContract(contractsDeployer: Contract, contractName: string): Promise {
+ // Fetch bytecode
+ const ContractFactory = await ethers.getContractFactory(contractName);
+ const bytecode = ContractFactory.bytecode;
+
+ // Deploy contract using ContractsDeployer
+ await contractsDeployer.deployContract(contractName, bytecode);
+
+ // Retrieve the deployed contract address
+ const address = await contractsDeployer.getContract(contractName);
+
+ // Load the contract
+ return ethers.getContractAt(contractName, address);
+}
+
+async function deployAndLoadContractWithArgs(contractsDeployer: Contract, contractName: string, constructorArgs: unknown[]): Promise {
+ // Create a ContractFactory with the ABI, bytecode, and constructor arguments
+ const ContractFactory = await ethers.getContractFactory(contractName);
+ const deployTx = ContractFactory.getDeployTransaction(...constructorArgs);
+
+ // Deploy contract using ContractsDeployer with the combined bytecode and constructor args
+ await contractsDeployer.deployContract(contractName, deployTx.data);
+
+ // Retrieve the deployed contract address
+ const address = await contractsDeployer.getContract(contractName);
+
+ // Load the contract
+ return ethers.getContractAt(contractName, address);
+}
+
export async function deployFullSuiteFixture() {
const [deployer, tokenIssuer, tokenAgent, tokenAdmin, claimIssuer, aliceWallet, bobWallet, charlieWallet, davidWallet, anotherWallet] =
await ethers.getSigners();
const claimIssuerSigningKey = ethers.Wallet.createRandom();
const aliceActionKey = ethers.Wallet.createRandom();
+ const contractsDeployer = await ethers.deployContract('ContractsDeployer', deployer);
+ await contractsDeployer.addAgent(deployer.address);
// Deploy implementations
- const claimTopicsRegistryImplementation = await ethers.deployContract('ClaimTopicsRegistry', deployer);
- const trustedIssuersRegistryImplementation = await ethers.deployContract('TrustedIssuersRegistry', deployer);
- const identityRegistryStorageImplementation = await ethers.deployContract('IdentityRegistryStorage', deployer);
- const identityRegistryImplementation = await ethers.deployContract('IdentityRegistry', deployer);
- const modularComplianceImplementation = await ethers.deployContract('ModularCompliance', deployer);
- const tokenImplementation = await ethers.deployContract('Token', deployer);
- const identityImplementation = await new ethers.ContractFactory(
- OnchainID.contracts.Identity.abi,
- OnchainID.contracts.Identity.bytecode,
- deployer,
- ).deploy(deployer.address, true);
-
- const identityImplementationAuthority = await new ethers.ContractFactory(
- OnchainID.contracts.ImplementationAuthority.abi,
- OnchainID.contracts.ImplementationAuthority.bytecode,
- deployer,
- ).deploy(identityImplementation.address);
+ const claimTopicsRegistryImplementation = await deployAndLoadContract(contractsDeployer, 'ClaimTopicsRegistry');
+ const trustedIssuersRegistryImplementation = await deployAndLoadContract(contractsDeployer, 'TrustedIssuersRegistry');
+ const identityRegistryStorageImplementation = await deployAndLoadContract(contractsDeployer, 'IdentityRegistryStorage');
+ const identityRegistryImplementation = await deployAndLoadContract(contractsDeployer, 'IdentityRegistry');
+ const modularComplianceImplementation = await deployAndLoadContract(contractsDeployer, 'ModularCompliance');
+ const tokenImplementation = await deployAndLoadContract(contractsDeployer, 'Token');
+ const identityImplementation = await deployAndLoadContractWithArgs(contractsDeployer, 'Identity', [deployer.address, true]);
+ const identityImplementationAuthority = await deployAndLoadContractWithArgs(contractsDeployer, 'ImplementationAuthority', [
+ identityImplementation.address,
+ ]);
+ await contractsDeployer.recoverContractOwnership(identityImplementationAuthority.address, deployer.address);
const identityFactory = await new ethers.ContractFactory(OnchainID.contracts.Factory.abi, OnchainID.contracts.Factory.bytecode, deployer).deploy(
identityImplementationAuthority.address,
);
- const trexImplementationAuthority = await ethers.deployContract(
- 'TREXImplementationAuthority',
- [true, ethers.constants.AddressZero, ethers.constants.AddressZero],
- deployer,
- );
+ const trexImplementationAuthority = await deployAndLoadContractWithArgs(contractsDeployer, 'TREXImplementationAuthority', [
+ true,
+ ethers.constants.AddressZero,
+ ethers.constants.AddressZero,
+ ]);
+ await contractsDeployer.recoverContractOwnership(trexImplementationAuthority.address, deployer.address);
const versionStruct = {
major: 4,
minor: 0,