diff --git a/.env.sample b/.env.sample index c5d4dc2..c3c977d 100644 --- a/.env.sample +++ b/.env.sample @@ -6,3 +6,9 @@ export API_KEY_ARBISCAN= export API_KEY_OPTIMISTIC_ETHERSCAN= export FOUNDRY_PROFILE="default" + +export DEPLOYER_ADDRESS= + +# Salts are 11 bytes +export SALT_DOCUMENT_STORE= +export SALT_TRANSFERABLE_DOCUMENT_STORE= diff --git a/script/DeployBase.s.sol b/script/DeployBase.s.sol new file mode 100644 index 0000000..ac9ed55 --- /dev/null +++ b/script/DeployBase.s.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.23 <0.9.0; + +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; + +abstract contract DeployBaseScript is Script { + address internal constant FACTORY = 0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed; + address internal constant DS_IMPL = 0xfC7C29C1f869C6DB5933235C8540Ff4526B26f69; + address internal constant TDS_IMPL = 0x8E9Eb7f019068f42E075f0a985D8A29596C82ED4; + + constructor() { + require(getDocumentStoreSalt() != getTransferableDocumentStoreSalt(), "Salts must be different"); + } + + function getSaltEntropy(string memory entropyName) internal view returns (bytes11) { + bytes11 entropy = bytes11(vm.envOr({name: entropyName, defaultValue: bytes("")})); + require(entropy != bytes11(0), "Salt entropy not set"); + + return entropy; + } + + function getSalt(bytes11 entropy) internal view returns (bytes32 salt) { + require(entropy != bytes11(0), "Salt entropy not be zero"); + + address deployer = vm.envOr({name: "DEPLOYER_ADDRESS", defaultValue: address(0)}); + require(deployer != address(0), "Deployer address not set"); + + bytes20 deployerBytes = bytes20(deployer); + + salt = bytes32(abi.encodePacked(deployerBytes, bytes1(0x00), entropy)); + } + + function getDocumentStoreSalt() internal view returns (bytes32 salt) { + bytes11 entropy = getSaltEntropy("SALT_DOCUMENT_STORE"); + salt = getSalt(entropy); + } + + function getTransferableDocumentStoreSalt() internal view returns (bytes32 salt) { + bytes11 entropy = getSaltEntropy("SALT_TRANSFERABLE_DOCUMENT_STORE"); + salt = getSalt(entropy); + } + + function dsImplExists() internal view returns (bool) { + return _exists(DS_IMPL); + } + + function tdsImplExists() internal view returns (bool) { + return _exists(TDS_IMPL); + } + + function computeAddr(bytes32 salt) internal view returns (address) { + bytes32 guardedSalt = _hash(bytes32(uint256(uint160(msg.sender))), salt); + (bool ok, bytes memory data) = FACTORY.staticcall( + abi.encodeWithSignature("computeCreate3Address(bytes32)", guardedSalt) + ); + require(ok, "Error compute DocumentStoreInitializable address"); + return abi.decode(data, (address)); + } + + function deploy(bytes32 salt, bytes memory initCode) internal returns (address) { + (bool ok, bytes memory data) = FACTORY.call( + abi.encodeWithSignature("deployCreate3(bytes32,bytes)", salt, initCode) + ); + require(ok, "Deployment failed"); + + return abi.decode(data, (address)); + } + + function clone(address impl, bytes memory initData) internal returns (address) { + (bool ok, bytes memory data) = FACTORY.call( + abi.encodeWithSignature("deployCreate2Clone(address,bytes)", impl, initData) + ); + require(ok, "Clone deployment failed"); + + return abi.decode(data, (address)); + } + + function _hash(bytes32 a, bytes32 b) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + mstore(0x00, a) + mstore(0x20, b) + hash := keccak256(0x00, 0x40) + } + } + + function _exists(address addr) internal view returns (bool) { + uint256 size; + assembly { + size := extcodesize(addr) + } + return size > 0; + } +} diff --git a/script/DocumentStore.s.sol b/script/DocumentStore.s.sol new file mode 100644 index 0000000..d21cd2e --- /dev/null +++ b/script/DocumentStore.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.23 <0.9.0; + +import "./DeployBase.s.sol"; +import "../src/DocumentStore.sol"; + +contract DocumentStoreScript is DeployBaseScript { + function run(string memory name, address admin) public returns (DocumentStore ds) { + _requireParams(name, admin); + + console2.log("DocumentStore Name: ", name); + console2.log("DocumentStore Admin: ", admin); + + if (dsImplExists()) { + bytes memory initData = abi.encodeWithSignature("initialize(string,address)", name, admin); + + vm.broadcast(); + address dsAddr = clone(DS_IMPL, initData); + + ds = DocumentStore(dsAddr); + } else { + vm.broadcast(); + ds = new DocumentStore(name, admin); + } + } + + function _requireParams(string memory name, address admin) private pure { + require(bytes(name).length > 0, "Name is required"); + require(admin != address(0), "Admin address is required"); + } +} diff --git a/script/DocumentStoreInitializable.s.sol b/script/DocumentStoreInitializable.s.sol new file mode 100644 index 0000000..531ad59 --- /dev/null +++ b/script/DocumentStoreInitializable.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.23 <0.9.0; + +import "./DeployBase.s.sol"; +import "../src/initializables/DocumentStoreInitializable.sol"; + +contract DocumentStoreInitializableScript is DeployBaseScript { + function run() public returns (DocumentStoreInitializable documentStore) { + require(!dsImplExists(), "DocumentStoreInitializable already exists"); + + bytes memory initCode = abi.encodePacked(type(DocumentStoreInitializable).creationCode); + + bytes32 dsSalt = getDocumentStoreSalt(); + + address computedAddr = computeAddr(dsSalt); + require(computedAddr == DS_IMPL, "Bad deployment address"); + + vm.broadcast(); + address dsAddr = deploy(dsSalt, initCode); + + documentStore = DocumentStoreInitializable(dsAddr); + } +} diff --git a/script/OwnableDocumentStore.s.sol b/script/OwnableDocumentStore.s.sol new file mode 100644 index 0000000..0307e3e --- /dev/null +++ b/script/OwnableDocumentStore.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.23 <0.9.0; + +import "./DeployBase.s.sol"; +import "../src/OwnableDocumentStore.sol"; + +contract OwnableDocumentStoreScript is DeployBaseScript { + function run(string memory name, string memory symbol, address admin) public returns (OwnableDocumentStore ds) { + _requireParams(name, symbol, admin); + + console2.log("OwnableDocumentStore Name: ", name); + console2.log("OwnableDocumentStore Symbol: ", symbol); + console2.log("OwnableDocumentStore Admin: ", admin); + + if (tdsImplExists()) { + bytes memory initData = abi.encodeWithSignature("initialize(string,string,address)", name, symbol, admin); + + vm.broadcast(); + address dsAddr = clone(TDS_IMPL, initData); + + ds = OwnableDocumentStore(dsAddr); + } else { + vm.broadcast(); + ds = new OwnableDocumentStore(name, symbol, admin); + } + } + + function _requireParams(string memory name, string memory symbol, address admin) private pure { + require(bytes(name).length > 0, "Name is required"); + require(bytes(symbol).length > 0, "Symbol is required"); + require(admin != address(0), "Admin address is required"); + } +} diff --git a/script/OwnableDocumentStoreInitializable.s.sol b/script/OwnableDocumentStoreInitializable.s.sol new file mode 100644 index 0000000..329a425 --- /dev/null +++ b/script/OwnableDocumentStoreInitializable.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.23 <0.9.0; + +import "./DeployBase.s.sol"; +import "../src/initializables/OwnableDocumentStoreInitializable.sol"; + +contract OwnableDocumentStoreInitializableScript is DeployBaseScript { + function run() public returns (OwnableDocumentStoreInitializable documentStore) { + require(!dsImplExists(), "OwnableDocumentStoreInitializable already exists"); + + bytes memory initCode = abi.encodePacked(type(OwnableDocumentStoreInitializable).creationCode); + + bytes32 dsSalt = getTransferableDocumentStoreSalt(); + + address computedAddr = computeAddr(dsSalt); + require(computedAddr == TDS_IMPL, "Bad deployment address"); + + vm.broadcast(); + address dsAddr = deploy(dsSalt, initCode); + + documentStore = OwnableDocumentStoreInitializable(dsAddr); + } +}