diff --git a/.github/workflows/lint_test.yaml b/.github/workflows/lint_test.yaml index b71d29c..3fd2896 100644 --- a/.github/workflows/lint_test.yaml +++ b/.github/workflows/lint_test.yaml @@ -47,10 +47,8 @@ jobs: - name: "Run the tests" run: "forge test" - - name: "Build the contracts" - run: | - forge --version - forge build --sizes --skip test/**/* + - name: "Check contract sizes" + run: "yarn run check-contract-sizes" - name: "Add test summary" run: | diff --git a/.prettierrc.yml b/.prettierrc.yml index c33ae4d..028dcf3 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -6,12 +6,21 @@ singleQuote: false tabWidth: 2 trailingComma: all -plugins: ["prettier-plugin-solidity"] +plugins: [prettier-plugin-solidity] overrides: - files: ["*.sol"] options: compiler: 0.5.17 + - files: [contracts/interfaces/*.sol] + options: + compiler: 0.8.18 + - files: + - contracts/interfaces/IBrokerAdmin.sol + - contracts/interfaces/ICeloToken.sol + - contracts/interfaces/IExchange.sol + options: + compiler: 0.5.17 - files: [contracts/tokens/patched/*.sol] options: compiler: 0.8.18 diff --git a/bin/check-contracts.sh b/bin/check-contracts.sh new file mode 100755 index 0000000..6b89269 --- /dev/null +++ b/bin/check-contracts.sh @@ -0,0 +1,65 @@ +#!/bin/bash +############################################################################## +# Sometimes when hitting AST compiler errors, the output doesn't tell you +# what file is causing the issue. This script helps you identify which +# contract is failing by compiling each contract individually. +############################################################################## + +# Get all contract files +IFS=$'\n' read -r -d '' -a contract_files < <(find contracts test -name "*.sol" && printf '\0') + +# Initialize an array to store contracts that need --via-ir +via_ir_contracts=() + +# Function to check if a contract builds without --via-ir +check_contract() { + local target_contract=$1 + local skip_contracts=() + + for contract in "${contract_files[@]}"; do + if [ "$contract" != "$target_contract" ]; then + skip_contracts+=("$contract") + fi + done + + forge clean + if forge build --skip ${skip_contracts[*]}; then + return 0 + else + return 1 + fi +} + +# Iterate through each contract +for contract in "${contract_files[@]}"; do + echo "----------------------------------------" + echo "Checking $contract..." + if check_contract "$contract"; then + echo "$contract does not require --via-ir" + else + echo "$contract requires --via-ir" + via_ir_contracts+=("$contract") + fi + echo "----------------------------------------" + echo +done + +# Print the results +if [ ${#via_ir_contracts[@]} -eq 0 ]; then + echo "All contracts can be built without --via-ir." +else + echo "The following contracts require --via-ir:" + printf '%s\n' "${via_ir_contracts[@]}" + echo + echo "Use the following command to build:" + echo -n "forge build --via-ir --skip " + + contracts_to_skip=() + for contract in "${contract_files[@]}"; do + if [[ ! " ${via_ir_contracts[*]} " =~ " ${contract} " ]]; then + contracts_to_skip+=("$contract") + fi + done + + echo "${contracts_to_skip[*]} test/**/*" +fi diff --git a/contracts/governance/Airgrab.sol b/contracts/governance/Airgrab.sol index 424b092..6629364 100644 --- a/contracts/governance/Airgrab.sol +++ b/contracts/governance/Airgrab.sol @@ -25,6 +25,20 @@ contract Airgrab is ReentrancyGuard { uint32 public constant MAX_CLIFF_PERIOD = 103; uint32 public constant MAX_SLOPE_PERIOD = 104; + /** + * @notice FractalProof struct for KYC/KYB verification. + * @param sig The signature of the Fractal Credential. + * @param validUntil The timestamp when the Fractal Credential expires. + * @param approvedAt The timestamp when the Fractal Credential was approved. + * @param fractalId The Fractal Credential ID. + */ + struct FractalProof { + bytes sig; + uint256 validUntil; + uint256 approvedAt; + string fractalId; + } + /** * @notice Emitted when tokens are claimed * @param claimer The account claiming the tokens @@ -68,31 +82,22 @@ contract Airgrab is ReentrancyGuard { * https://github.com/trustfractal/credentials-api-verifiers * @notice This function checks the kyc signature with the data provided. * @param account The address of the account to check. - * @param proof The kyc proof for the account. - * @param validUntil The kyc proof valid until timestamp. - * @param approvedAt The kyc proof approved at timestamp. - * @param fractalId The kyc proof fractal id. + * @param proof FractaLProof kyc proof data for the account. */ - modifier hasValidKyc( - address account, - bytes memory proof, - uint256 validUntil, - uint256 approvedAt, - string memory fractalId - ) { - require(block.timestamp < validUntil, "Airgrab: KYC no longer valid"); - require(fractalMaxAge == 0 || block.timestamp < approvedAt + fractalMaxAge, "Airgrab: KYC not recent enough"); + modifier hasValidKyc(address account, FractalProof memory proof) { + require(block.timestamp < proof.validUntil, "Airgrab: KYC no longer valid"); + require(fractalMaxAge == 0 || block.timestamp < proof.approvedAt + fractalMaxAge, "Airgrab: KYC not recent enough"); string memory accountString = Strings.toHexString(uint256(uint160(account)), 20); bytes32 signedMessageHash = ECDSA.toEthSignedMessageHash( abi.encodePacked( accountString, ";", - fractalId, + proof.fractalId, ";", - Strings.toString(approvedAt), + Strings.toString(proof.approvedAt), ";", - Strings.toString(validUntil), + Strings.toString(proof.validUntil), ";", // ISO 3166-1 alpha-2 country codes // DRC, CUBA, GB, IRAN, DPKR, MALI, MYANMAR, SOUTH SUDAN, SYRIA, US, YEMEN @@ -100,7 +105,7 @@ contract Airgrab is ReentrancyGuard { ) ); - require(SignatureChecker.isValidSignatureNow(fractalSigner, signedMessageHash, proof), "Airgrab: Invalid KYC"); + require(SignatureChecker.isValidSignatureNow(fractalSigner, signedMessageHash, proof.sig), "Airgrab: Invalid KYC"); _; } @@ -178,26 +183,23 @@ contract Airgrab is ReentrancyGuard { * @param delegate The address of the account that gets voting power delegated * @param merkleProof The merkle proof for the account. * @param fractalProof The Fractal KYC proof for the account. - * @param fractalProofValidUntil The Fractal KYC proof valid until timestamp. - * @param fractalProofApprovedAt The Fractal KYC proof approved at timestamp. - * @param fractalId The Fractal KYC ID. */ function claim( uint96 amount, address delegate, bytes32[] calldata merkleProof, - bytes calldata fractalProof, - uint256 fractalProofValidUntil, - uint256 fractalProofApprovedAt, - string memory fractalId - ) - external - hasValidKyc(msg.sender, fractalProof, fractalProofValidUntil, fractalProofApprovedAt, fractalId) - canClaim(msg.sender, amount, merkleProof) - nonReentrant - { + FractalProof calldata fractalProof + ) external hasValidKyc(msg.sender, fractalProof) canClaim(msg.sender, amount, merkleProof) nonReentrant { require(token.balanceOf(address(this)) >= amount, "Airgrab: insufficient balance"); + _claim(amount, delegate); + } + /** + * @dev Internal function to claim tokens and lock them. + * @param amount The amount of tokens to be claimed. + * @param delegate The address of the account that gets voting power delegated + */ + function _claim(uint96 amount, address delegate) internal { claimed[msg.sender] = true; uint256 lockId = locking.lock(msg.sender, delegate, amount, slopePeriod, cliffPeriod); emit TokensClaimed(msg.sender, amount, lockId); diff --git a/contracts/governance/GovernanceFactory.sol b/contracts/governance/GovernanceFactory.sol index 736cb84..7ea85a5 100644 --- a/contracts/governance/GovernanceFactory.sol +++ b/contracts/governance/GovernanceFactory.sol @@ -51,6 +51,16 @@ contract GovernanceFactory is Ownable { uint256[] additionalAllocationAmounts; } + /// @dev Precalculated addresses by nonce for the contracts to be deployed + struct PrecalculatedAddresses { + address mentoToken; + address emission; + address airgrab; + address locking; + address governanceTimelock; + address mentoGovernor; + } + ProxyAdmin public proxyAdmin; MentoToken public mentoToken; Emission public emission; @@ -109,22 +119,47 @@ contract GovernanceFactory is Ownable { // slither-disable-next-line missing-zero-check watchdogMultiSig = watchdogMultiSig_; - // Precalculated contract addresses: - address tokenPrecalculated = addressForNonce(2); - address emissionPrecalculated = addressForNonce(4); - address airgrabPrecalculated = addressForNonce(5); - address lockingPrecalculated = addressForNonce(7); - address governanceTimelockPrecalculated = addressForNonce(9); - address governorPrecalculated = addressForNonce(11); + PrecalculatedAddresses memory addr = getPrecalculatedAddresses(); - address[] memory owners = new address[](1); - owners[0] = governanceTimelockPrecalculated; + deployProxyAdmin(); + deployMentoToken(allocationParams, addr); + deployEmission(addr); + deployAirgrab(airgrabRoot, fractalSigner, addr); + deployLocking(addr); + deployTimelock(addr); + deployMentoGovernor(addr); + transferOwnership(); + + emit GovernanceCreated( + address(proxyAdmin), + address(emission), + address(mentoToken), + address(airgrab), + address(locking), + address(governanceTimelock), + address(mentoGovernor) + ); + } + /** + * @notice Deploys the ProxyAdmin contract. + */ + function deployProxyAdmin() internal { // ========================================= // ========== Deploy 1: ProxyAdmin ========= // ========================================= proxyAdmin = ProxyDeployerLib.deployAdmin(); // NONCE:1 + } + /** + * @notice Deploys the MentoToken contract. + * @param allocationParams Parameters for the initial token allocation + * @param addr Precalculated addresses for the contracts to be deployed. + */ + function deployMentoToken( + MentoTokenAllocationParams memory allocationParams, + PrecalculatedAddresses memory addr + ) internal { // =========================================== // ========== Deploy 2: MentoToken =========== // =========================================== @@ -132,9 +167,9 @@ contract GovernanceFactory is Ownable { address[] memory allocationRecipients = new address[](numberOfRecipients); uint256[] memory allocationAmounts = new uint256[](numberOfRecipients); - allocationRecipients[0] = airgrabPrecalculated; + allocationRecipients[0] = addr.airgrab; allocationAmounts[0] = allocationParams.airgrabAllocation; - allocationRecipients[1] = governanceTimelockPrecalculated; + allocationRecipients[1] = addr.governanceTimelock; allocationAmounts[1] = allocationParams.mentoTreasuryAllocation; for (uint256 i = 0; i < allocationParams.additionalAllocationRecipients.length; i++) { @@ -142,15 +177,16 @@ contract GovernanceFactory is Ownable { allocationAmounts[i + 2] = allocationParams.additionalAllocationAmounts[i]; } - mentoToken = MentoTokenDeployerLib.deploy( // NONCE:2 - allocationRecipients, - allocationAmounts, - emissionPrecalculated, - lockingPrecalculated - ); + mentoToken = MentoTokenDeployerLib.deploy(allocationRecipients, allocationAmounts, addr.emission, addr.locking); // NONCE:2 - assert(address(mentoToken) == tokenPrecalculated); + assert(address(mentoToken) == addr.mentoToken); + } + /** + * @notice Deploys the Emission contract. + * @param addr Precalculated addresses for the contracts to be deployed. + */ + function deployEmission(PrecalculatedAddresses memory addr) internal { // ========================================= // ========== Deploy 3: Emission =========== // ========================================= @@ -161,15 +197,23 @@ contract GovernanceFactory is Ownable { address(proxyAdmin), abi.encodeWithSelector( emissionImpl.initialize.selector, - tokenPrecalculated, /// @param mentoToken_ The address of the MentoToken contract. - governanceTimelockPrecalculated, /// @param governanceTimelock_ The address of the mento treasury contract. + addr.mentoToken, /// @param mentoToken_ The address of the MentoToken contract. + addr.governanceTimelock, /// @param governanceTimelock_ The address of the mento treasury contract. mentoToken.emissionSupply() /// @param emissionSupply_ The total amount of tokens that can be emitted. ) ); emission = Emission(address(emissionProxy)); - assert(address(emission) == emissionPrecalculated); + assert(address(emission) == addr.emission); + } + /** + * @notice Deploys the Airgrab contract. + * @param airgrabRoot Root hash for the airgrab Merkle tree. + * @param fractalSigner Signer of fractal kyc. + * @param addr Precalculated addresses for the contracts to be deployed. + */ + function deployAirgrab(bytes32 airgrabRoot, address fractalSigner, PrecalculatedAddresses memory addr) internal { // ======================================== // ========== Deploy 4: Airgrab =========== // ======================================== @@ -182,12 +226,18 @@ contract GovernanceFactory is Ownable { airgrabEnds, AIRGRAB_LOCK_CLIFF, AIRGRAB_LOCK_SLOPE, - tokenPrecalculated, - lockingPrecalculated, - payable(governanceTimelockPrecalculated) + addr.mentoToken, + addr.locking, + payable(addr.governanceTimelock) ); - assert(address(airgrab) == airgrabPrecalculated); + assert(address(airgrab) == addr.airgrab); + } + /** + * @notice Deploys the Locking contract. + * @param addr Precalculated addresses for the contracts to be deployed. + */ + function deployLocking(PrecalculatedAddresses memory addr) internal { // ========================================== // ========== Deploy 5-6: Locking =========== // ========================================== @@ -206,8 +256,14 @@ contract GovernanceFactory is Ownable { ) ); locking = Locking(address(lockingProxy)); - assert(address(locking) == lockingPrecalculated); + assert(address(locking) == addr.locking); + } + /** + * @notice Deploys the Timelock Controller and Governance Timelock contracts. + * @param addr Precalculated addresses for the contracts to be deployed. + */ + function deployTimelock(PrecalculatedAddresses memory addr) internal { // =================================================================== // ========== Deploy 7: Timelock Controller Implementation =========== // =================================================================== @@ -219,7 +275,7 @@ contract GovernanceFactory is Ownable { // ==================================================== address[] memory governanceProposers = new address[](1); address[] memory governanceExecutors = new address[](1); - governanceProposers[0] = governorPrecalculated; // Only MentoGovernor can propose + governanceProposers[0] = addr.mentoGovernor; // Only MentoGovernor can propose governanceExecutors[0] = address(0); // Anyone can execute passed proposals // slither-disable-next-line reentrancy-benign @@ -236,8 +292,14 @@ contract GovernanceFactory is Ownable { ) ); governanceTimelock = TimelockController(payable(governanceTimelockProxy)); - assert(address(governanceTimelock) == governanceTimelockPrecalculated); + assert(address(governanceTimelock) == addr.governanceTimelock); + } + /** + * @notice Deploys the MentoGovernor contract. + * @param addr Precalculated addresses for the contracts to be deployed. + */ + function deployMentoGovernor(PrecalculatedAddresses memory addr) internal { // ================================================== // ========== Deploy 9-10: Mento Governor =========== // ================================================== @@ -248,17 +310,24 @@ contract GovernanceFactory is Ownable { address(proxyAdmin), abi.encodeWithSelector( mentoGovernorImpl.__MentoGovernor_init.selector, - address(lockingProxy), /// @param veToken The escrowed Mento Token used for voting. - governanceTimelockProxy, /// @param timelockController The timelock controller used by the governor. + address(locking), /// @param veToken The escrowed Mento Token used for voting. + address(governanceTimelock), /// @param timelockController The timelock controller used by the governor. GOVERNOR_VOTING_DELAY, /// @param votingDelay_ The delay time in blocks between the proposal creation and the start of voting. GOVERNOR_VOTING_PERIOD, /// @param votingPeriod_ The voting duration in blocks between the vote start and vote end. GOVERNOR_PROPOSAL_THRESHOLD, /// @param threshold_ The number of votes required in order for a voter to become a proposer. GOVERNOR_QUORUM /// @param quorum_ The minimum number of votes in percent of total supply required in order for a proposal to succeed. ) ); + + // slither-disable-next-line reentrancy-benign mentoGovernor = MentoGovernor(payable(mentoGovernorProxy)); - assert(address(mentoGovernor) == governorPrecalculated); + assert(address(mentoGovernor) == addr.mentoGovernor); + } + /** + * @notice Transfers the ownership of the contracts to the governance timelock. + */ + function transferOwnership() internal { // ============================================= // =========== Configure Ownership ============= // ============================================= @@ -266,16 +335,22 @@ contract GovernanceFactory is Ownable { locking.transferOwnership(address(governanceTimelock)); proxyAdmin.transferOwnership(address(governanceTimelock)); mentoToken.transferOwnership(address(governanceTimelock)); + } - emit GovernanceCreated( - address(proxyAdmin), - address(emission), - address(mentoToken), - address(airgrab), - address(locking), - address(governanceTimelock), - address(mentoGovernor) - ); + /** + * @notice Returns the precalculated addresses for the contracts to be deployed. + * @return The precalculated addresses. + */ + function getPrecalculatedAddresses() internal view returns (PrecalculatedAddresses memory) { + return + PrecalculatedAddresses({ + mentoToken: addressForNonce(2), + emission: addressForNonce(4), + airgrab: addressForNonce(5), + locking: addressForNonce(7), + governanceTimelock: addressForNonce(9), + mentoGovernor: addressForNonce(11) + }); } /** diff --git a/contracts/interfaces/IStableTokenV2.sol b/contracts/interfaces/IStableTokenV2.sol index c6e0e61..cbf4941 100644 --- a/contracts/interfaces/IStableTokenV2.sol +++ b/contracts/interfaces/IStableTokenV2.sol @@ -54,13 +54,8 @@ interface IStableTokenV2 { function initialize( string calldata _name, string calldata _symbol, - uint8, // deprecated: decimals - address, // deprecated: registryAddress, - uint256, // deprecated: inflationRate, - uint256, // deprecated: inflationFactorUpdatePeriod, address[] calldata initialBalanceAddresses, - uint256[] calldata initialBalanceValues, - string calldata // deprecated: exchangeIdentifier + uint256[] calldata initialBalanceValues ) external; /** diff --git a/contracts/interfaces/IStableTokenV2DeprecatedInit.sol b/contracts/interfaces/IStableTokenV2DeprecatedInit.sol new file mode 100644 index 0000000..c852b79 --- /dev/null +++ b/contracts/interfaces/IStableTokenV2DeprecatedInit.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import { IStableTokenV2 } from "./IStableTokenV2.sol"; + +/** + * @title IStableTokenV2DeprecatedInit + * @notice Interface for the deprecated initialize function in StableTokenV2 + * @dev In order to improve our DX and get rid of `via-ir` interfaces we + * are deprecating the old initialize function in favor of the new one. + * Keeping this interface for backwards compatibility, in fork tests, + * because in practice we will never be able to call this function again, anyway. + * More details: https://github.com/mento-protocol/mento-core/pull/502 + */ +interface IStableTokenV2DeprecatedInit is IStableTokenV2 { + function initialize( + string calldata _name, + string calldata _symbol, + uint8, // deprecated: decimals + address, // deprecated: registryAddress, + uint256, // deprecated: inflationRate, + uint256, // deprecated: inflationFactorUpdatePeriod, + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues, + string calldata // deprecated: exchangeIdentifier + ) external; +} diff --git a/contracts/tokens/StableTokenV2.sol b/contracts/tokens/StableTokenV2.sol index 54f9ab5..edcb9f3 100644 --- a/contracts/tokens/StableTokenV2.sol +++ b/contracts/tokens/StableTokenV2.sol @@ -75,13 +75,8 @@ contract StableTokenV2 is ERC20PermitUpgradeable, IStableTokenV2, CalledByVm { string calldata _name, string calldata _symbol, // slither-disable-end shadowing-local - uint8, // deprecated: decimals - address, // deprecated: registryAddress, - uint256, // deprecated: inflationRate, - uint256, // deprecated: inflationFactorUpdatePeriod, address[] calldata initialBalanceAddresses, - uint256[] calldata initialBalanceValues, - string calldata // deprecated: exchangeIdentifier + uint256[] calldata initialBalanceValues ) external initializer { __ERC20_init_unchained(_name, _symbol); __ERC20Permit_init(_symbol); diff --git a/foundry.toml b/foundry.toml index b631be6..9c05e19 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,7 +9,6 @@ bytecode_hash = "none" fuzz_runs = 256 gas_reports = ["*"] optimizer = false -optimizer_runs = 200 legacy = true no_match_contract = "(ForkTest)|(GovernanceGasTest)" @@ -18,25 +17,17 @@ allow_paths = [ ] fs_permissions = [ - { access = "read", path = "out" }, - { access = "read-write", path = "test/fixtures" } -] - -additional_compiler_profiles = [ - { name = "via-ir-opt", via_ir = true, optimizer = true } -] - -compilation_restrictions = [ - { paths = "contracts/governance/Airgrab.sol", via_ir = true, optimizer = true }, - { paths = "contracts/tokens/StableTokenV2.sol", via_ir = true, optimizer = true }, + { access = "read", path = "out" } ] [profile.ci] -via_ir=true -optimizer=true fuzz_runs = 1_000 verbosity = 3 +[profile.optimized] +optimizer = true +optimizer_runs = 200 + [profile.fork-tests] no_match_contract = "_random" # in order to reset the no_match_contract match_contract = "ForkTest" diff --git a/package.json b/package.json index 7170c96..a551331 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,9 @@ "fork-test": "env FOUNDRY_PROFILE=fork-tests forge test", "fork-test:baklava": "env FOUNDRY_PROFILE=fork-tests forge test --match-contract Baklava", "fork-test:alfajores": "env FOUNDRY_PROFILE=fork-tests forge test --match-contract Alfajores", - "fork-test:celo-mainnet": "env FOUNDRY_PROFILE=fork-tests forge test --match-contract CeloMainnet" + "fork-test:celo-mainnet": "env FOUNDRY_PROFILE=fork-tests forge test --match-contract CeloMainnet", + "check-no-ir": "./bin/check-contracts.sh", + "check-contract-sizes": "env FOUNDRY_PROFILE=optimized forge build --sizes --skip test/**/*" }, "dependencies": { "@celo/contracts": "^11.0.0" diff --git a/test/fork/ChainForkTest.sol b/test/fork/ChainForkTest.sol index 0b9b802..1617f29 100644 --- a/test/fork/ChainForkTest.sol +++ b/test/fork/ChainForkTest.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8; import "./BaseForkTest.sol"; import { IBiPoolManager } from "contracts/interfaces/IBiPoolManager.sol"; -import { IStableTokenV2 } from "contracts/interfaces/IStableTokenV2.sol"; +import { IStableTokenV2DeprecatedInit } from "contracts/interfaces/IStableTokenV2DeprecatedInit.sol"; contract ChainForkTest is BaseForkTest { using FixidityLib for FixidityLib.Fraction; @@ -58,11 +58,21 @@ contract ChainForkTest is BaseForkTest { } function test_stableTokensCanNotBeReinitialized() public { - IStableTokenV2 stableToken = IStableTokenV2(registry.getAddressForStringOrDie("StableToken")); - IStableTokenV2 stableTokenEUR = IStableTokenV2(registry.getAddressForStringOrDie("StableTokenEUR")); - IStableTokenV2 stableTokenBRL = IStableTokenV2(registry.getAddressForStringOrDie("StableTokenBRL")); - IStableTokenV2 stableTokenXOF = IStableTokenV2(registry.getAddressForStringOrDie("StableTokenXOF")); - IStableTokenV2 stableTokenKES = IStableTokenV2(registry.getAddressForStringOrDie("StableTokenKES")); + IStableTokenV2DeprecatedInit stableToken = IStableTokenV2DeprecatedInit( + registry.getAddressForStringOrDie("StableToken") + ); + IStableTokenV2DeprecatedInit stableTokenEUR = IStableTokenV2DeprecatedInit( + registry.getAddressForStringOrDie("StableTokenEUR") + ); + IStableTokenV2DeprecatedInit stableTokenBRL = IStableTokenV2DeprecatedInit( + registry.getAddressForStringOrDie("StableTokenBRL") + ); + IStableTokenV2DeprecatedInit stableTokenXOF = IStableTokenV2DeprecatedInit( + registry.getAddressForStringOrDie("StableTokenXOF") + ); + IStableTokenV2DeprecatedInit stableTokenKES = IStableTokenV2DeprecatedInit( + registry.getAddressForStringOrDie("StableTokenKES") + ); vm.expectRevert("Initializable: contract is already initialized"); stableToken.initialize("", "", 8, address(10), 0, 0, new address[](0), new uint256[](0), ""); diff --git a/test/integration/governance/GovernanceIntegration.t.sol b/test/integration/governance/GovernanceIntegration.t.sol index adaede5..7d51976 100644 --- a/test/integration/governance/GovernanceIntegration.t.sol +++ b/test/integration/governance/GovernanceIntegration.t.sol @@ -294,16 +294,16 @@ contract GovernanceIntegrationTest is GovernanceTest { bytes[] memory calldatas, string memory description ) = Proposals._proposeChangeSettings( - mentoGovernor, - governanceTimelock, - locking, - newVotingDelay, - newVotingPeriod, - newThreshold, - newQuorum, - newMinDelay, - newMinCliff, - newMinSlope + Proposals.changeSettingsContracts(mentoGovernor, governanceTimelock, locking), + Proposals.changeSettingsVars( + newVotingDelay, + newVotingPeriod, + newThreshold, + newQuorum, + newMinDelay, + newMinCliff, + newMinSlope + ) ); // ~10 mins @@ -341,16 +341,16 @@ contract GovernanceIntegrationTest is GovernanceTest { vm.prank(alice); vm.expectRevert("Governor: proposer votes below proposal threshold"); Proposals._proposeChangeSettings( - mentoGovernor, - governanceTimelock, - locking, - newVotingDelay, - newVotingPeriod, - newThreshold, - newQuorum, - newMinDelay, - newMinCliff, - newMinSlope + Proposals.changeSettingsContracts(mentoGovernor, governanceTimelock, locking), + Proposals.changeSettingsVars( + newVotingDelay, + newVotingPeriod, + newThreshold, + newQuorum, + newMinDelay, + newMinCliff, + newMinSlope + ) ); // Lock reverts because new min period is higher vm.prank(alice); @@ -382,14 +382,24 @@ contract GovernanceIntegrationTest is GovernanceTest { // slope = 104 // cliff = 0 vm.prank(claimer0); - airgrab.claim(claimer0Amount, claimer0, claimer0Proof, fractalProof0, validUntil, approvedAt, "fractalId"); + airgrab.claim( + claimer0Amount, + claimer0, + claimer0Proof, + Airgrab.FractalProof(fractalProof0, validUntil, approvedAt, "fractalId") + ); // claimer1Amount = 20_000e18 // slope = 104 // cliff = 0 // claim with a delegate vm.prank(claimer1); - airgrab.claim(claimer1Amount, alice, claimer1Proof, fractalProof1, validUntil, approvedAt, "fractalId"); + airgrab.claim( + claimer1Amount, + alice, + claimer1Proof, + Airgrab.FractalProof(fractalProof1, validUntil, approvedAt, "fractalId") + ); // claimed amounts are locked automatically // 100e18 * (103 / 103) = 100e18 @@ -541,16 +551,19 @@ contract GovernanceIntegrationTest is GovernanceTest { function test_governor_propose_whenExecutedForImplementationUpgrade_shouldUpgradeTheContracts() public s_governance { // create new implementations - LockingHarness newLockingContract = new LockingHarness(); - TimelockController newGovernanceTimelockContract = new TimelockController(); - MentoGovernor newGovernorContract = new MentoGovernor(); - Emission newEmissionContract = new Emission(false); + address[] memory newImplementations = addresses( + address(new LockingHarness()), + address(new TimelockController()), + address(new MentoGovernor()), + address(new Emission(false)) + ); - // proxies of current implementations - ITransparentUpgradeableProxy lockingProxy = ITransparentUpgradeableProxy(address(locking)); - ITransparentUpgradeableProxy governanceTimelockProxy = ITransparentUpgradeableProxy(governanceTimelockAddress); - ITransparentUpgradeableProxy mentoGovernorProxy = ITransparentUpgradeableProxy(address(mentoGovernor)); - ITransparentUpgradeableProxy emissionProxy = ITransparentUpgradeableProxy(address(emission)); + address[] memory proxies = addresses( + address(locking), + governanceTimelockAddress, + address(mentoGovernor), + address(emission) + ); vm.prank(alice); ( @@ -559,18 +572,7 @@ contract GovernanceIntegrationTest is GovernanceTest { uint256[] memory values, bytes[] memory calldatas, string memory description - ) = Proposals._proposeUpgradeContracts( - mentoGovernor, - proxyAdmin, - lockingProxy, - governanceTimelockProxy, - mentoGovernorProxy, - emissionProxy, - address(newLockingContract), - address(newGovernanceTimelockContract), - address(newGovernorContract), - address(newEmissionContract) - ); + ) = Proposals._proposeUpgradeContracts(mentoGovernor, proxyAdmin, proxies, newImplementations); // ~10 mins vm.timeTravel(120); @@ -595,13 +597,10 @@ contract GovernanceIntegrationTest is GovernanceTest { mentoGovernor.execute(targets, values, calldatas, keccak256(bytes(description))); - assertEq(address(proxyAdmin.getProxyImplementation(lockingProxy)), address(newLockingContract)); - assertEq( - address(proxyAdmin.getProxyImplementation(governanceTimelockProxy)), - address(newGovernanceTimelockContract) - ); - assertEq(address(proxyAdmin.getProxyImplementation(mentoGovernorProxy)), address(newGovernorContract)); - assertEq(address(proxyAdmin.getProxyImplementation(emissionProxy)), address(newEmissionContract)); + for (uint256 i = 0; i < proxies.length; i++) { + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(proxies[i]); + assertEq(address(proxyAdmin.getProxyImplementation(proxy)), newImplementations[i]); + } // new implementation has the method and governance upgraded the contract LockingHarness(address(locking)).setEpochShift(1); diff --git a/test/integration/governance/Proposals.sol b/test/integration/governance/Proposals.sol index d0b8b85..da109a3 100644 --- a/test/integration/governance/Proposals.sol +++ b/test/integration/governance/Proposals.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.18; import { uints, addresses, bytesList } from "mento-std/Array.sol"; import { ProxyAdmin } from "openzeppelin-contracts-next/contracts/proxy/transparent/ProxyAdmin.sol"; -import { ITransparentUpgradeableProxy } from "openzeppelin-contracts-next/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { MentoGovernor } from "contracts/governance/MentoGovernor.sol"; import { TimelockController } from "contracts/governance/TimelockController.sol"; @@ -35,17 +34,25 @@ library Proposals { proposalId = mentoGovernor.propose(targets, values, calldatas, description); } + struct changeSettingsVars { + uint256 votingDelay; + uint256 votingPeriod; + uint256 threshold; + uint256 quorum; + uint256 minDelay; + uint32 minCliff; + uint32 minSlope; + } + + struct changeSettingsContracts { + MentoGovernor mentoGovernor; + TimelockController timelockController; + Locking locking; + } + function _proposeChangeSettings( - MentoGovernor mentoGovernor, - TimelockController timelockController, - Locking locking, - uint256 votingDelay, - uint256 votingPeriod, - uint256 threshold, - uint256 quorum, - uint256 minDelay, - uint32 minCliff, - uint32 minSlope + changeSettingsContracts memory _targets, + changeSettingsVars memory vars ) internal returns ( @@ -57,40 +64,34 @@ library Proposals { ) { targets = addresses( - address(mentoGovernor), - address(mentoGovernor), - address(mentoGovernor), - address(mentoGovernor), - address(timelockController), - address(locking), - address(locking) + address(_targets.mentoGovernor), + address(_targets.mentoGovernor), + address(_targets.mentoGovernor), + address(_targets.mentoGovernor), + address(_targets.timelockController), + address(_targets.locking), + address(_targets.locking) ); values = uints(0, 0, 0, 0, 0, 0, 0); calldatas = bytesList( - abi.encodeWithSelector(mentoGovernor.setVotingDelay.selector, votingDelay), - abi.encodeWithSelector(mentoGovernor.setVotingPeriod.selector, votingPeriod), - abi.encodeWithSelector(mentoGovernor.setProposalThreshold.selector, threshold), - abi.encodeWithSelector(mentoGovernor.updateQuorumNumerator.selector, quorum), - abi.encodeWithSelector(timelockController.updateDelay.selector, minDelay), - abi.encodeWithSelector(locking.setMinCliffPeriod.selector, minCliff), - abi.encodeWithSelector(locking.setMinSlopePeriod.selector, minSlope) + abi.encodeWithSelector(_targets.mentoGovernor.setVotingDelay.selector, vars.votingDelay), + abi.encodeWithSelector(_targets.mentoGovernor.setVotingPeriod.selector, vars.votingPeriod), + abi.encodeWithSelector(_targets.mentoGovernor.setProposalThreshold.selector, vars.threshold), + abi.encodeWithSelector(_targets.mentoGovernor.updateQuorumNumerator.selector, vars.quorum), + abi.encodeWithSelector(_targets.timelockController.updateDelay.selector, vars.minDelay), + abi.encodeWithSelector(_targets.locking.setMinCliffPeriod.selector, vars.minCliff), + abi.encodeWithSelector(_targets.locking.setMinSlopePeriod.selector, vars.minSlope) ); description = "Change governance config"; - proposalId = mentoGovernor.propose(targets, values, calldatas, description); + proposalId = _targets.mentoGovernor.propose(targets, values, calldatas, description); } function _proposeUpgradeContracts( MentoGovernor mentoGovernor, ProxyAdmin proxyAdmin, - ITransparentUpgradeableProxy proxy0, - ITransparentUpgradeableProxy proxy1, - ITransparentUpgradeableProxy proxy2, - ITransparentUpgradeableProxy proxy3, - address newImpl0, - address newImpl1, - address newImpl2, - address newImpl3 + address[] memory proxies, + address[] memory newImplementations ) internal returns ( @@ -101,14 +102,13 @@ library Proposals { string memory description ) { - targets = addresses(address(proxyAdmin), address(proxyAdmin), address(proxyAdmin), address(proxyAdmin)); - values = uints(0, 0, 0, 0); - calldatas = bytesList( - abi.encodeWithSelector(proxyAdmin.upgrade.selector, proxy0, newImpl0), - abi.encodeWithSelector(proxyAdmin.upgrade.selector, proxy1, newImpl1), - abi.encodeWithSelector(proxyAdmin.upgrade.selector, proxy2, newImpl2), - abi.encodeWithSelector(proxyAdmin.upgrade.selector, proxy3, newImpl3) - ); + targets = new address[](proxies.length); + calldatas = new bytes[](proxies.length); + values = new uint256[](proxies.length); + for (uint256 i = 0; i < proxies.length; i++) { + targets[i] = address(proxyAdmin); + calldatas[i] = abi.encodeWithSelector(proxyAdmin.upgrade.selector, proxies[i], newImplementations[i]); + } description = "Upgrade upgradeable contracts"; proposalId = mentoGovernor.propose(targets, values, calldatas, description); diff --git a/test/integration/protocol/ProtocolTest.sol b/test/integration/protocol/ProtocolTest.sol index 0e18fae..0405855 100644 --- a/test/integration/protocol/ProtocolTest.sol +++ b/test/integration/protocol/ProtocolTest.sol @@ -97,45 +97,15 @@ contract ProtocolTest is Test, WithRegistry { uint256[] memory initialBalances = new uint256[](0); cUSDToken = IStableTokenV2(deployCode("StableTokenV2", abi.encode(false))); - cUSDToken.initialize( - "cUSD", - "cUSD", - 18, - CELO_REGISTRY_ADDRESS, - FixidityLib.unwrap(FixidityLib.fixed1()), - 60 * 60 * 24 * 7, - initialAddresses, - initialBalances, - "Exchange" - ); + cUSDToken.initialize("cUSD", "cUSD", initialAddresses, initialBalances); cUSDToken.initializeV2(address(broker), address(0x0), address(0x0)); cEURToken = IStableTokenV2(deployCode("StableTokenV2", abi.encode(false))); - cEURToken.initialize( - "cEUR", - "cEUR", - 18, - CELO_REGISTRY_ADDRESS, - FixidityLib.unwrap(FixidityLib.fixed1()), - 60 * 60 * 24 * 7, - initialAddresses, - initialBalances, - "Exchange" - ); + cEURToken.initialize("cEUR", "cEUR", initialAddresses, initialBalances); cEURToken.initializeV2(address(broker), address(0x0), address(0x0)); eXOFToken = IStableTokenV2(deployCode("StableTokenV2", abi.encode(false))); - eXOFToken.initialize( - "eXOF", - "eXOF", - 18, - CELO_REGISTRY_ADDRESS, - FixidityLib.unwrap(FixidityLib.fixed1()), - 60 * 60 * 24 * 7, - initialAddresses, - initialBalances, - "Exchange" - ); + eXOFToken.initialize("eXOF", "eXOF", initialAddresses, initialBalances); eXOFToken.initializeV2(address(broker), address(0x0), address(0x0)); vm.label(address(cUSDToken), "cUSD"); diff --git a/test/unit/governance/Airgrab.t.sol b/test/unit/governance/Airgrab.t.sol index 4f69362..1236d40 100644 --- a/test/unit/governance/Airgrab.t.sol +++ b/test/unit/governance/Airgrab.t.sol @@ -257,10 +257,12 @@ contract AirgrabTest is Test { cl_params.amount, cl_params.delegate, cl_params.merkleProof, - cl_params.fractalProof, - cl_params.fractalProofValidUntil, - cl_params.fractalProofApprovedAt, - cl_params.fractalId + Airgrab.FractalProof( + cl_params.fractalProof, + cl_params.fractalProofValidUntil, + cl_params.fractalProofApprovedAt, + cl_params.fractalId + ) ); } diff --git a/test/unit/tokens/StableTokenV2.t.sol b/test/unit/tokens/StableTokenV2.t.sol index 0909341..1876c84 100644 --- a/test/unit/tokens/StableTokenV2.t.sol +++ b/test/unit/tokens/StableTokenV2.t.sol @@ -44,13 +44,8 @@ contract StableTokenV2Test is Test { token.initialize( "cUSD", "cUSD", - 0, // deprecated - address(0), // deprecated - 0, // deprecated - 0, // deprecated addresses(holder0, holder1, holder2, broker, exchange), - uints(1000, 1000, 1000, 1000, 1000), - "" // deprecated + uints(1000, 1000, 1000, 1000, 1000) ); token.initializeV2(broker, validators, exchange); } @@ -69,17 +64,7 @@ contract StableTokenV2Test is Test { uint256[] memory initialBalances = new uint256[](0); vm.expectRevert(bytes("Initializable: contract is already initialized")); - disabledToken.initialize( - "cUSD", - "cUSD", - 0, // deprecated - address(0), // deprecated - 0, // deprecated - 0, // deprecated - initialAddresses, - initialBalances, - "" // deprecated - ); + disabledToken.initialize("cUSD", "cUSD", initialAddresses, initialBalances); vm.expectRevert(bytes("Initializable: contract is already initialized")); token.initializeV2(broker, validators, exchange); @@ -263,7 +248,7 @@ contract StableTokenV2Test is Test { assertEq(token.totalSupply(), tokenSupplyBefore + newlyMinted - baseTxFee); } - function test_creditGasFees_whenCalledByVm_withMultiple0xRecipients_shouldBurnTheirRespectiveFees() public { + function test_creditGasFees_whenCalledByVm_withMultiple0xRecipients_shouldBurnTheirRespectiveFees0() public { uint256 refund = 20; uint256 tipTxFee = 30; uint256 gatewayFee = 10; @@ -281,7 +266,13 @@ contract StableTokenV2Test is Test { assertEq(token.balanceOf(gatewayFeeRecipient), 0); assertEq(token.balanceOf(communityFund), 0); assertEq(token.totalSupply(), tokenSupplyBefore0 + newlyMinted0 - gatewayFee - baseTxFee); + } + function test_creditGasFees_whenCalledByVm_withMultiple0xRecipients_shouldBurnTheirRespectiveFees1() public { + uint256 refund = 20; + uint256 tipTxFee = 30; + uint256 gatewayFee = 10; + uint256 baseTxFee = 40; // case with both feeRecipient and communityFund both 0x uint256 holder1InitialBalance = token.balanceOf(holder1); uint256 feeRecipientBalance = token.balanceOf(feeRecipient);