diff --git a/.gitignore b/.gitignore index 136b6d7..c0b72bb 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,6 @@ lib .hardhat/contracts # Deployment files -**deployments/test.json +**deployments/test/*.json **/tmp/* !**/tmp/example.json diff --git a/foundry.toml b/foundry.toml index 773eb1d..e04919c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,6 +3,7 @@ optimizer_runs = 1000000 verbosity = 1 libs = ["lib"] fs_permissions = [{ access = "read-write", path = "./"}] +ffi = true bytecode_hash="none" solc_version="0.8.19" diff --git a/package.json b/package.json index f9573d9..3544517 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "scripts": { "test": "forge test", + "save-deployments": "./script/bash/save-deployments.sh", "lint": "prettier --write **.sol" } } diff --git a/script/BaseConfig.sol b/script/BaseConfig.sol new file mode 100644 index 0000000..2a1fdc7 --- /dev/null +++ b/script/BaseConfig.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Script} from "forge-std/Script.sol"; +import "forge-std/console.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +struct DeploymentConfig { + address authRequestBuilder; + address claimRequestBuilder; + address requestBuilder; + address signatureBuilder; +} + +contract BaseDeploymentConfig is Script { + DeploymentConfig public config; + + string public _chainName; + bool public _checkIfEmpty; + + address immutable SISMO_ADDRESSES_PROVIDER_V2 = 0x3Cd5334eB64ebBd4003b72022CC25465f1BFcEe6; + address immutable ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; + + error ChainNameNotFound(string chainName); + + function _setConfig(string memory chainName) internal { + if ( + _compareStrings(chainName, "mainnet") || + _compareStrings(chainName, "gnosis") || + _compareStrings(chainName, "polygon") || + _compareStrings(chainName, "optimism") || + _compareStrings(chainName, "arbitrum-one") || + _compareStrings(chainName, "testnet-goerli") || + _compareStrings(chainName, "testnet-sepolia") || + _compareStrings(chainName, "testnet-mumbai") || + _compareStrings(chainName, "optimism-goerli") || + _compareStrings(chainName, "arbitrum-goerli") || + _compareStrings(chainName, "scroll-testnet-goerli") || + _compareStrings(chainName, "staging-goerli") || + _compareStrings(chainName, "staging-mumbai") + ) { + config = _readDeploymentConfig( + string.concat(vm.projectRoot(), "/deployments/", chainName, ".json") + ); + } else if (_compareStrings(chainName, "test")) { + config = DeploymentConfig(address(0), address(0), address(0), address(0)); + } else { + revert ChainNameNotFound(chainName); + } + } + + function _readDeploymentConfig( + string memory filePath + ) internal view returns (DeploymentConfig memory) { + string memory file = _tryReadingFile(filePath); + return + DeploymentConfig({ + authRequestBuilder: _tryReadingAddressFromFileAtKey(file, ".authRequestBuilder"), + claimRequestBuilder: _tryReadingAddressFromFileAtKey(file, ".claimRequestBuilder"), + requestBuilder: _tryReadingAddressFromFileAtKey(file, ".requestBuilder"), + signatureBuilder: _tryReadingAddressFromFileAtKey(file, ".signatureBuilder") + }); + } + + function _tryReadingFile(string memory filePath) internal view returns (string memory) { + try vm.readFile(filePath) returns (string memory file) { + return file; + } catch { + return ""; + } + } + + function _tryReadingAddressFromFileAtKey( + string memory file, + string memory key + ) internal view returns (address) { + try vm.parseJson(file, key) returns (bytes memory encodedAddress) { + console.logBytes(encodedAddress); + return + keccak256(encodedAddress) == keccak256(abi.encodePacked((""))) + ? address(0) + : abi.decode(encodedAddress, (address)); + } catch { + return ZERO_ADDRESS; + } + } + + function _saveDeploymentConfig(string memory chainName) internal { + _createFolderIfItDoesNotExists(string.concat(vm.projectRoot(), "/deployments")); + _createFolderIfItDoesNotExists(string.concat(vm.projectRoot(), "/deployments/tmp")); + _createFolderIfItDoesNotExists(string.concat(vm.projectRoot(), "/deployments/tmp/", chainName)); + + vm.serializeAddress(chainName, "authRequestBuilder", address(config.authRequestBuilder)); + vm.serializeAddress(chainName, "claimRequestBuilder", address(config.claimRequestBuilder)); + vm.serializeAddress(chainName, "requestBuilder", address(config.requestBuilder)); + string memory finalJson = vm.serializeAddress( + chainName, + "signatureBuilder", + address(config.signatureBuilder) + ); + + vm.writeJson( + finalJson, + string.concat( + vm.projectRoot(), + "/deployments/tmp/", + chainName, + "/logs", + "-", + Strings.toString(block.timestamp), + ".json" + ) + ); + } + + function _createFolderIfItDoesNotExists(string memory folderPath) internal { + string[] memory inputs = new string[](3); + inputs[0] = "mkdir"; + inputs[1] = "-p"; + inputs[2] = folderPath; + vm.ffi(inputs); + } + + function _compareStrings(string memory a, string memory b) internal pure returns (bool) { + return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); + } +} diff --git a/script/DeployLibraries.s.sol b/script/DeployLibraries.s.sol new file mode 100644 index 0000000..364f8e5 --- /dev/null +++ b/script/DeployLibraries.s.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import "forge-std/Script.sol"; +import "forge-std/console.sol"; +import {BaseDeploymentConfig, DeploymentConfig} from "script/BaseConfig.sol"; +import {IAddressesProvider} from "src/interfaces/IAddressesProvider.sol"; +import {AuthRequestBuilder} from "src/utils/AuthRequestBuilder.sol"; +import {ClaimRequestBuilder} from "src/utils/ClaimRequestBuilder.sol"; +import {SignatureBuilder} from "src/utils/SignatureBuilder.sol"; +import {RequestBuilder} from "src/utils/RequestBuilder.sol"; + +/** + * @title DeployLibraries Script + * @notice To test this script locally, run: + * in a first terminal: `anvil fork-url https://rpc.ankr.com/polygon_mumbai` + * in a second terminal: `cast rpc anvil_impersonateAccount` + * and finally: `CHAIN_NAME=test forge script DeployLibraries --rpc-url localhost --sender 0xbb8fca8f2381cfeede5d7541d7bf76343ef6c67b --unlocked -vvvv` + * You should see that the libraries are being deployed and successfully set in the AddressesProvider contract. + * You can then run the `yarn save-deployments` command to save the deployment addresses in the `deployments` folder. You will see the libraries addresses in the `deployments/test/logs-*.json` file. + */ + +contract DeployLibraries is Script, BaseDeploymentConfig { + struct DeployedLibraries { + AuthRequestBuilder authRequestBuilder; + ClaimRequestBuilder claimRequestBuilder; + SignatureBuilder signatureBuilder; + RequestBuilder requestBuilder; + } + + struct DeployedLibrary { + address addr; + string name; + } + + // useful variables to set libraries addresses in AddressesProvider in batch + address[] public contractAddresses; + string[] public contractNames; + + function run() public { + runFor(vm.envString("CHAIN_NAME")); + } + + function runFor(string memory chainName) public returns (DeploymentConfig memory) { + console.log("Run for CHAIN_NAME:", chainName); + console.log("Deployer:", msg.sender); + + vm.startBroadcast(); + + _setConfig({chainName: chainName}); + + // deploy external libraries + DeployedLibraries memory deployedLibraries; + deployedLibraries.authRequestBuilder = _deployAuthRequestBuilder(); + deployedLibraries.claimRequestBuilder = _deployClaimRequestBuilder(); + deployedLibraries.signatureBuilder = _deploySignatureBuilder(); + deployedLibraries.requestBuilder = _deployRequestBuilder(); + + // associate libraries addresses to names + DeployedLibrary[] memory deployedLibrariesArray = new DeployedLibrary[](4); + deployedLibrariesArray[0] = DeployedLibrary({ + addr: address(deployedLibraries.authRequestBuilder), + name: "authRequestBuilder-v1.1" + }); + deployedLibrariesArray[1] = DeployedLibrary({ + addr: address(deployedLibraries.claimRequestBuilder), + name: "claimRequestBuilder-v1.1" + }); + deployedLibrariesArray[2] = DeployedLibrary({ + addr: address(deployedLibraries.signatureBuilder), + name: "signatureBuilder-v1.1" + }); + deployedLibrariesArray[3] = DeployedLibrary({ + addr: address(deployedLibraries.requestBuilder), + name: "requestBuilder-v1.1" + }); + + // update addresses provider with deployed libraries addresses + _setLibrariesAddresses(deployedLibrariesArray); + + // update deployment config with deployed libraries addresses + // and save it in `deployments` folder + // only for a successful broadcast + config = DeploymentConfig({ + authRequestBuilder: address(deployedLibraries.authRequestBuilder), + claimRequestBuilder: address(deployedLibraries.claimRequestBuilder), + signatureBuilder: address(deployedLibraries.signatureBuilder), + requestBuilder: address(deployedLibraries.requestBuilder) + }); + vm.stopBroadcast(); + _saveDeploymentConfig(chainName); + + return config; + } + + function _deployAuthRequestBuilder() private returns (AuthRequestBuilder) { + address authRequestBuilderAddress = config.authRequestBuilder; + if (authRequestBuilderAddress != address(0)) { + console.log("Using existing authrequestBuilder:", authRequestBuilderAddress); + return AuthRequestBuilder(authRequestBuilderAddress); + } + AuthRequestBuilder authRequestBuilder = new AuthRequestBuilder(); + console.log("authRequestBuilder Deployed:", address(authRequestBuilder)); + return authRequestBuilder; + } + + function _deployClaimRequestBuilder() private returns (ClaimRequestBuilder) { + address claimRequestBuilderAddress = config.claimRequestBuilder; + if (claimRequestBuilderAddress != address(0)) { + console.log("Using existing claimRequestBuilder:", claimRequestBuilderAddress); + return ClaimRequestBuilder(claimRequestBuilderAddress); + } + ClaimRequestBuilder claimRequestBuilder = new ClaimRequestBuilder(); + console.log("claimRequestBuilder Deployed:", address(claimRequestBuilder)); + return claimRequestBuilder; + } + + function _deploySignatureBuilder() private returns (SignatureBuilder) { + address signatureBuilderAddress = config.signatureBuilder; + if (signatureBuilderAddress != address(0)) { + console.log("Using existing signatureBuilder:", signatureBuilderAddress); + return SignatureBuilder(signatureBuilderAddress); + } + SignatureBuilder signatureBuilder = new SignatureBuilder(); + console.log("signatureBuilder Deployed:", address(signatureBuilder)); + return signatureBuilder; + } + + function _deployRequestBuilder() private returns (RequestBuilder) { + address requestBuilderAddress = config.requestBuilder; + if (requestBuilderAddress != address(0)) { + console.log("Using existing requestBuilder:", requestBuilderAddress); + return RequestBuilder(requestBuilderAddress); + } + RequestBuilder requestBuilder = new RequestBuilder(); + console.log("requestBuilder Deployed:", address(requestBuilder)); + return requestBuilder; + } + + function _setLibrariesAddresses(DeployedLibrary[] memory deployedLibrariesArray) private { + console.log("== Updating Addresses Provider =="); + IAddressesProvider sismoAddressProvider = IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2); + + for (uint256 i = 0; i < deployedLibrariesArray.length; i++) { + DeployedLibrary memory deployedLibrary = deployedLibrariesArray[i]; + address currentContractAddress = sismoAddressProvider.get(deployedLibrary.name); + + if (currentContractAddress != deployedLibrary.addr) { + console.log( + "current contract address for", + deployedLibrary.name, + "is different. Updating address to", + deployedLibrary.addr + ); + // save address to update in batch after + contractAddresses.push(deployedLibrary.addr); + contractNames.push(deployedLibrary.name); + } else { + console.log( + "current contract address for", + deployedLibrary.name, + "is already the expected one. skipping update" + ); + } + } + + if (contractAddresses.length > 0) { + console.log("Updating Addresses Provider in batch..."); + sismoAddressProvider.setBatch(contractAddresses, contractNames); + } + } +} diff --git a/script/bash/save-deployments.sh b/script/bash/save-deployments.sh new file mode 100755 index 0000000..52bf4ab --- /dev/null +++ b/script/bash/save-deployments.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +source_folder="$PWD/deployments/tmp" +destination_root="$PWD/deployments" + +# Find all subfolders containing JSON files and process them +find "$source_folder" -type f -name "*.json" | while read -r json_file; do + subfolder=$(dirname "$json_file") + subfolder_name=$(basename "$subfolder") + destination_folder="$destination_root/$subfolder_name" + + # Create the destination folder after deleting the previous one if it exists + if [ -d "$destination_folder" ]; then rm -Rf $destination_folder; fi + mkdir "$destination_folder" + + # Find the latest JSON file in the subfolder + latest_file=$(ls -t "$subfolder"/*.json | head -n 1) + + if [ -n "$latest_file" ]; then + # Copy the latest JSON file to the destination folder to keep track of the latest deployment + cp "$latest_file" "$destination_folder" + else + echo "No matching files found in $subfolder_name." + fi +done \ No newline at end of file diff --git a/test/BaseTest.t.sol b/test/BaseTest.t.sol index d87b906..6432615 100644 --- a/test/BaseTest.t.sol +++ b/test/BaseTest.t.sol @@ -14,7 +14,7 @@ contract BaseTest is Test { address immutable user1 = vm.addr(1); address immutable user2 = vm.addr(2); address immutable owner = vm.addr(3); - address immutable sismoAddressProviderV2 = 0x3Cd5334eB64ebBd4003b72022CC25465f1BFcEe6; + address public immutable SISMO_ADDRESSES_PROVIDER_V2 = 0x3Cd5334eB64ebBd4003b72022CC25465f1BFcEe6; SismoConnectVerifierMock sismoConnectVerifier; @@ -34,25 +34,25 @@ contract BaseTest is Test { signatureBuilder = new SignatureBuilder(); requestBuilder = new RequestBuilder(); - vm.etch(sismoAddressProviderV2, address(addressesProviderMock).code); + vm.etch(SISMO_ADDRESSES_PROVIDER_V2, address(addressesProviderMock).code); - IAddressesProvider(sismoAddressProviderV2).set( + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).set( address(sismoConnectVerifier), string("sismoConnectVerifier-v1.2") ); - IAddressesProvider(sismoAddressProviderV2).set( + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).set( address(authRequestBuilder), string("authRequestBuilder-v1.1") ); - IAddressesProvider(sismoAddressProviderV2).set( + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).set( address(claimRequestBuilder), string("claimRequestBuilder-v1.1") ); - IAddressesProvider(sismoAddressProviderV2).set( + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).set( address(signatureBuilder), string("signatureBuilder-v1.1") ); - IAddressesProvider(sismoAddressProviderV2).set( + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).set( address(requestBuilder), string("requestBuilder-v1.1") ); diff --git a/test/mocks/AddressesProviderMock.sol b/test/mocks/AddressesProviderMock.sol index 975ffd9..ea43efe 100644 --- a/test/mocks/AddressesProviderMock.sol +++ b/test/mocks/AddressesProviderMock.sol @@ -20,6 +20,22 @@ contract AddressesProviderMock { _contractAddresses[contractNameHash] = contractAddress; } + /** + * @dev Sets the address of multiple contracts. + * @param contractAddresses Addresses of the contracts. + * @param contractNames Names of the contracts. + */ + function setBatch(address[] calldata contractAddresses, string[] calldata contractNames) public { + require( + contractAddresses.length == contractNames.length, + "AddressesProviderMock: Arrays must be the same length" + ); + + for (uint256 i = 0; i < contractAddresses.length; i++) { + set(contractAddresses[i], contractNames[i]); + } + } + function get(string memory contractName) public view returns (address) { bytes32 contractNameHash = keccak256(abi.encodePacked(contractName)); diff --git a/test/script/DeployLibraries.t.sol b/test/script/DeployLibraries.t.sol new file mode 100644 index 0000000..3b03fcc --- /dev/null +++ b/test/script/DeployLibraries.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {DeployLibraries, DeploymentConfig} from "script/DeployLibraries.s.sol"; +import {AddressesProviderMock} from "test/mocks/AddressesProviderMock.sol"; +import {BaseTest} from "test/BaseTest.t.sol"; +import {IAddressesProvider} from "src/interfaces/IAddressesProvider.sol"; + +contract DeployLibrariesTest is BaseTest { + DeployLibraries deploy; + DeploymentConfig contracts; + + function setUp() public virtual override { + super.setUp(); + + deploy = new DeployLibraries(); + // deploy all libraries by calling the `runFor` function of the DeployLibraries script contract + (bool success, bytes memory result) = address(deploy).delegatecall( + abi.encodeWithSelector(DeployLibraries.runFor.selector, "test") + ); + require(success, "Deploy script did not run successfully!"); + contracts = abi.decode(result, (DeploymentConfig)); + } + + function testDeployLibraries() public { + // check that the libraries were deployed + // and that the addresses in the AddressesProvider were updated accordingly + assertEq( + address(contracts.authRequestBuilder), + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).get(string("authRequestBuilder-v1.1")) + ); + assertFalse(address(contracts.authRequestBuilder) == address(authRequestBuilder)); + + assertEq( + address(contracts.claimRequestBuilder), + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).get(string("claimRequestBuilder-v1.1")) + ); + assertFalse(address(contracts.claimRequestBuilder) == address(claimRequestBuilder)); + + assertEq( + address(contracts.signatureBuilder), + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).get(string("signatureBuilder-v1.1")) + ); + assertFalse(address(contracts.signatureBuilder) == address(signatureBuilder)); + + assertEq( + address(contracts.requestBuilder), + IAddressesProvider(SISMO_ADDRESSES_PROVIDER_V2).get(string("requestBuilder-v1.1")) + ); + assertFalse(address(contracts.requestBuilder) == address(requestBuilder)); + } +}