From adf02396a90e360891b81fda9d4030b1b8b87624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 5 Aug 2025 16:11:00 +0200 Subject: [PATCH 01/34] poc for operations script with foundry. --- .gitignore | 8 +++ foundry.toml | 36 +++++++++++++ foundry/.env | 4 ++ foundry/scripts/runlogs/2025.s.sol | 76 +++++++++++++++++++++++++++ foundry/scripts/runlogs/result.json | 28 ++++++++++ foundry/scripts/runlogs/test.json | 42 +++++++++++++++ foundry/scripts/utils/Addresses.sol | 26 ++++++++++ foundry/scripts/utils/JsonWriter.sol | 77 ++++++++++++++++++++++++++++ foundry/scripts/utils/Setup.sol | 55 ++++++++++++++++++++ 9 files changed, 352 insertions(+) create mode 100644 foundry.toml create mode 100644 foundry/.env create mode 100644 foundry/scripts/runlogs/2025.s.sol create mode 100644 foundry/scripts/runlogs/result.json create mode 100644 foundry/scripts/runlogs/test.json create mode 100644 foundry/scripts/utils/Addresses.sol create mode 100644 foundry/scripts/utils/JsonWriter.sol create mode 100644 foundry/scripts/utils/Setup.sol diff --git a/.gitignore b/.gitignore index 847bb491de..f2833c5baa 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,9 @@ contracts/.localKeyValueStorage contracts/.localKeyValueStorage.mainnet contracts/.localKeyValueStorage.holesky contracts/scripts/defender-actions/dist/ +cache/ +foundry/broadcast/ +foundry/out/ contracts/lib/defender-actions/dist/ @@ -89,3 +92,8 @@ unit-coverage # Certora # .certora_internal + + +# Soldeer +foundry/dependencies +soldeer.lock diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000000..78e31d4a94 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,36 @@ +[profile.default] +src = "contracts/contracts" +out = "foundry/out" +script = "foundry/scripts" +libs = ["dependencies"] +broadcast = "foundry/broadcast" +verbosity = 3 +auto_detect_remappings = false +viaIR = true +etherscan_api_key = "${ETHERSCAN_API_KEY}" +fs_permissions = [{ access = "read", path = "./"}] + +remappings = [ + "@openzeppelin/=contracts/node_modules/@openzeppelin", + "@chainlink/=contracts/node_modules/@chainlink", + "@layerzerolabs/=contracts/node_modules/@layerzerolabs", + "hardhat/=contracts/node_modules/hardhat", + "solidity-bytes-utils/=contracts/node_modules/solidity-bytes-utils/", + "forge-std/=foundry/dependencies/forge-std-1.10.0/src", +] + +[fmt] +line_length = 100 +tab_width = 2 +bracket_spacing = true + +[dependencies] +forge-std = "1.10.0" + +[soldeer] +recursive_deps = false +remappings_version = false +remappings_generate = false +remappings_regenerate = false +remappings_prefix = "@" +remappings_location = "config" diff --git a/foundry/.env b/foundry/.env new file mode 100644 index 0000000000..6afb6583a4 --- /dev/null +++ b/foundry/.env @@ -0,0 +1,4 @@ + +MAINNET_BLOCK_NUMBER=23073300 + +ETHERSCAN_API_KEY=XSK3578ESKKSXH85ZZZZ1FGZ4JBWZQT1CX \ No newline at end of file diff --git a/foundry/scripts/runlogs/2025.s.sol b/foundry/scripts/runlogs/2025.s.sol new file mode 100644 index 0000000000..1cd56ec576 --- /dev/null +++ b/foundry/scripts/runlogs/2025.s.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +// Helpers +import { SetupMainnet } from "../utils/Setup.sol"; + +// Foundry +import { console } from "forge-std/console.sol"; + +contract Runlogs_2025_08 is SetupMainnet { + function run() public { + _2025_08_04(); + } + + function _2025_08_04() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToWithdraw = 1_000 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToWithdraw; + oethVaultAdmin.withdrawFromStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 1 ether); + + console.log("-----"); + console.log("Profit %18e: ", profit); + console.log("OETH Supply change %18e: ", supplyChange); + console.log("Vault value change %18e: ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + + // Todo: fetch the JSON output from the run and get path dynamically + // Todo: migrate this in a modifier + string memory json = vm.readFile("foundry/broadcast/2025.s.sol/1/dry-run/run-latest.json"); + testTransformJson(json); + } +} diff --git a/foundry/scripts/runlogs/result.json b/foundry/scripts/runlogs/result.json new file mode 100644 index 0000000000..6f8ee8c75a --- /dev/null +++ b/foundry/scripts/runlogs/result.json @@ -0,0 +1,28 @@ +{ + "version": "1.0", + "chainId": "1", + "createdAt": 1754400248404, + "meta": { + "name": "Transactions Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "0x4ff1b9d9ba8558f5eafcec096318ea0d8b541971", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "0x39254033945aa2e4809cc2977e7087bee48bd7ab", + "value": "0", + "data": "0xaf14052c", + "contractMethod": null, + "contractInputsValues": null + }, + { + "to": "0x31fd8618379d8e473ec2b1540b906e8e11d2a99b", + "value": "0", + "data": "0xb3d3d37e", + "contractMethod": null, + "contractInputsValues": null + } + ] +} \ No newline at end of file diff --git a/foundry/scripts/runlogs/test.json b/foundry/scripts/runlogs/test.json new file mode 100644 index 0000000000..ab1b042915 --- /dev/null +++ b/foundry/scripts/runlogs/test.json @@ -0,0 +1,42 @@ +{ + "version": "1.0", + "chainId": "1", + "createdAt": 1754386908, + "meta": { + "name": "Transactions Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab", + "value": "0", + "data": "0xaf14052c", + "contractMethod": null, + "contractInputsValues": null + }, + { + "to": "0x31FD8618379D8e473Ec2B1540B906E8e11D2A99b", + "value": "0", + "data": "0xb3d3d37e", + "contractMethod": null, + "contractInputsValues": null + }, + { + "to": "0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab", + "value": "0", + "data": "0xae69f3cb000000000000000000000000ba0e352ab5c13861c26e4e773e7a833c3a223fe6000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001158e460913d00000", + "contractMethod": null, + "contractInputsValues": null + }, + { + "to": "0x31FD8618379D8e473Ec2B1540B906E8e11D2A99b", + "value": "0", + "data": "0xb1d79df5000000000000000000000000000000000000000000000000000647a8bcdb29740000000000000000000000000000000000000000000000000de0b6b3a7640000fffffffffffffffffffffffffffffffffffffffffffffffe049024756c12df8c0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "contractMethod": null, + "contractInputsValues": null + } + ] +} \ No newline at end of file diff --git a/foundry/scripts/utils/Addresses.sol b/foundry/scripts/utils/Addresses.sol new file mode 100644 index 0000000000..f03663d8cf --- /dev/null +++ b/foundry/scripts/utils/Addresses.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +library Mainnet { + // Governance + address public constant MULTICHAIN_STRATEGIST = 0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971; + + // OUSD + address public constant OUSD = 0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86; + + // OETH + address public constant OETH = 0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3; + address public constant WOETH = 0xDcEe70654261AF21C44c093C300eD3Bb97b78192; + address public constant OETH_VAULT = 0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab; + address public constant OETH_VAULT_VALUE_CHECKER = 0x31FD8618379D8e473Ec2B1540B906E8e11D2A99b; + + // OETH Strategies + address public constant OETH_WETH_CURVE_AMO = 0xba0e352AB5c13861C26e4E773e7a833C3A223FE6; + + // Other token + address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + // Curve pools + address public constant OETH_WETH_CURVE_POOL = 0xcc7d5785AD5755B6164e21495E07aDb0Ff11C2A8; +} diff --git a/foundry/scripts/utils/JsonWriter.sol b/foundry/scripts/utils/JsonWriter.sol new file mode 100644 index 0000000000..6333214dd1 --- /dev/null +++ b/foundry/scripts/utils/JsonWriter.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { stdJson } from "forge-std/StdJson.sol"; +import { Vm } from "forge-std/Vm.sol"; +import { console } from "forge-std/console.sol"; + +abstract contract JsonWriter { + using stdJson for string; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function testTransformJson(string memory inputJson) public pure { + string memory chainId = stdJson.readString(inputJson, ".chain"); + uint256 timestamp = stdJson.readUint(inputJson, ".timestamp"); + + // Format métadonnées + string memory meta = string.concat( + "{", + '"name":"Transactions Batch",', + '"description":"",', + '"txBuilderVersion":"1.16.1",', + '"createdFromSafeAddress":"', + stdJson.readString(inputJson, ".transactions[0].transaction.from"), + '",', + '"createdFromOwnerAddress":""', + "}" + ); + + // Format des transactions + // Todo: handle multiple transactions dynamically + string memory tx0 = formatTx(inputJson, 0); + string memory tx1 = formatTx(inputJson, 1); + + string memory allTxs = string.concat("[", tx0, ",", tx1, "]"); + + // Final JSON + string memory finalJson = string.concat( + "{", + '"version":"1.0",', + '"chainId":"', + chainId, + '",', + '"createdAt":', + vm.toString(timestamp), + ",", + '"meta":', + meta, + ",", + '"transactions":', + allTxs, + "}" + ); + + console.log(finalJson); + } + + function formatTx(string memory json, uint256 index) internal pure returns (string memory) { + string memory prefix = string.concat(".transactions[", vm.toString(index), "].transaction"); + string memory to = stdJson.readString(json, string.concat(prefix, ".to")); + string memory input = stdJson.readString(json, string.concat(prefix, ".input")); + + return string.concat( + "{", + '"to":"', + to, + '",', + '"value":"0",', + '"data":"', + input, + '",', + '"contractMethod":null,', + '"contractInputsValues":null', + "}" + ); + } +} diff --git a/foundry/scripts/utils/Setup.sol b/foundry/scripts/utils/Setup.sol new file mode 100644 index 0000000000..c16ddd8153 --- /dev/null +++ b/foundry/scripts/utils/Setup.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +// Contracts - OUSD +import { OUSD } from "contracts/contracts/token/OUSD.sol"; + +// Contracts - OETH +import { OETH } from "contracts/contracts/token/OETH.sol"; +import { WOETH } from "contracts/contracts/token/WOETH.sol"; +import { OETHVaultCore } from "contracts/contracts/vault/OETHVaultCore.sol"; +import { OETHVaultAdmin } from "contracts/contracts/vault/OETHVaultAdmin.sol"; +import { OETHVaultValueChecker } from "contracts/contracts/strategies/VaultValueChecker.sol"; + +// Contracts - Strategies +import { CurveAMOStrategy } from "contracts/contracts/strategies/CurveAMOStrategy.sol"; + +// Interfaces +import { IWETH9 } from "contracts/contracts/interfaces/IWETH9.sol"; +import { ICurveStableSwapNG } from "contracts/contracts/interfaces/ICurveStableSwapNG.sol"; + +// Helpers +import { Mainnet } from "./Addresses.sol"; +import { JsonWriter } from "../utils/JsonWriter.sol"; +import { Script } from "forge-std/Script.sol"; +import { Test } from "forge-std/Test.sol"; + +// Foundry + +abstract contract SetupMainnet is JsonWriter, Test, Script { + // Governance + address public strategist = Mainnet.MULTICHAIN_STRATEGIST; + + // OUSD + OUSD public ousd = OUSD(Mainnet.OUSD); + + // OETH + OETH public oeth = OETH(Mainnet.OETH); + WOETH public woeth = WOETH(Mainnet.WOETH); + OETHVaultCore public oethVaultCore = OETHVaultCore(Mainnet.OETH_VAULT); + OETHVaultAdmin public oethVaultAdmin = OETHVaultAdmin(Mainnet.OETH_VAULT); + CurveAMOStrategy public oethWethCurveAMO = CurveAMOStrategy(Mainnet.OETH_WETH_CURVE_AMO); + OETHVaultValueChecker public oethVaultValueChecker = + OETHVaultValueChecker(Mainnet.OETH_VAULT_VALUE_CHECKER); + + // Interfaces + IWETH9 public weth = IWETH9(Mainnet.WETH); + ICurveStableSwapNG public oethWethCurvePool = ICurveStableSwapNG(Mainnet.OETH_WETH_CURVE_POOL); + + function setUp() public { + // Todo: fetch url and block number dynamically + vm.createSelectFork( + "url", 23073300 + ); + } +} From f15d55c18a188542b2ca89bdbd54ae9edb067f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 12 Aug 2025 10:04:33 +0200 Subject: [PATCH 02/34] chore: update .gitignore, refactor foundry.toml, and add JSON transformation script --- .gitignore | 2 + foundry.toml | 2 +- foundry/.env | 4 - foundry/helpers/broadcast_convertor.py | 107 +++++++++++++++++++++++++ foundry/scripts/runlogs/2025.s.sol | 7 +- foundry/scripts/utils/JsonWriter.sol | 77 ------------------ foundry/scripts/utils/Setup.sol | 6 +- 7 files changed, 113 insertions(+), 92 deletions(-) delete mode 100644 foundry/.env create mode 100644 foundry/helpers/broadcast_convertor.py delete mode 100644 foundry/scripts/utils/JsonWriter.sol diff --git a/.gitignore b/.gitignore index f2833c5baa..7ee6786cd1 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,8 @@ contracts/scripts/defender-actions/dist/ cache/ foundry/broadcast/ foundry/out/ +dependencies/ +.env contracts/lib/defender-actions/dist/ diff --git a/foundry.toml b/foundry.toml index 78e31d4a94..2abb06d1c7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -16,7 +16,7 @@ remappings = [ "@layerzerolabs/=contracts/node_modules/@layerzerolabs", "hardhat/=contracts/node_modules/hardhat", "solidity-bytes-utils/=contracts/node_modules/solidity-bytes-utils/", - "forge-std/=foundry/dependencies/forge-std-1.10.0/src", + "forge-std/=dependencies/forge-std-1.10.0/src", ] [fmt] diff --git a/foundry/.env b/foundry/.env deleted file mode 100644 index 6afb6583a4..0000000000 --- a/foundry/.env +++ /dev/null @@ -1,4 +0,0 @@ - -MAINNET_BLOCK_NUMBER=23073300 - -ETHERSCAN_API_KEY=XSK3578ESKKSXH85ZZZZ1FGZ4JBWZQT1CX \ No newline at end of file diff --git a/foundry/helpers/broadcast_convertor.py b/foundry/helpers/broadcast_convertor.py new file mode 100644 index 0000000000..d838d18687 --- /dev/null +++ b/foundry/helpers/broadcast_convertor.py @@ -0,0 +1,107 @@ +import json +import sys +import os +import argparse + +def parse_arguments(): + """Parse command line arguments""" + parser = argparse.ArgumentParser(description='Transform JSON format for Safe transactions') + parser.add_argument( + '--input', '-i', + required=True, + help='Path to input JSON file' + ) + parser.add_argument( + '--output', '-o', + help='Path to output JSON file (default: adds -safe to input filename)' + ) + parser.add_argument( + '--suffix', + default='safe', + help='Suffix to add to filename (default: safe)' + ) + return parser.parse_args() + +def load_input_json(file_path): + """Load JSON from file""" + try: + with open(file_path, 'r') as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: File '{file_path}' not found") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in file '{file_path}': {e}") + sys.exit(1) + +def transform_json(input_data): + """Transform input JSON to target format""" + # Create output structure + output_json = { + "version": "1.0", + "chainId": str(input_data["chain"]), + "createdAt": input_data["timestamp"] // 1000, # Convert ms to seconds + "meta": { + "name": "Transactions Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "", + "createdFromOwnerAddress": "" + }, + "transactions": [] + } + + # Extract Safe address from first transaction (from field) + if input_data["transactions"]: + safe_address = input_data["transactions"][0]["transaction"]["from"] + output_json["meta"]["createdFromSafeAddress"] = safe_address + + # Transform each transaction + for tx in input_data["transactions"]: + transaction = tx["transaction"] + + # Convert hex value to integer then to string + value = str(int(transaction["value"], 16)) + + transformed_tx = { + "to": transaction["to"], + "value": value, + "data": transaction["input"], + "contractMethod": None, + "contractInputsValues": None + } + + output_json["transactions"].append(transformed_tx) + + return output_json + +def get_output_path(input_path, suffix, custom_output=None): + """Generate output path by adding suffix before file extension""" + if custom_output: + return custom_output + + name, ext = os.path.splitext(input_path) + return f"{name}-{suffix}{ext}" + +def main(): + """Main function""" + args = parse_arguments() + + # Load input JSON + input_data = load_input_json(args.input) + + # Transform JSON + result = transform_json(input_data) + + # Generate output path and save + output_path = get_output_path(args.input, args.suffix, args.output) + try: + with open(output_path, 'w') as f: + json.dump(result, f, indent=2) + print(f"Successfully transformed and saved to: {output_path}") + except Exception as e: + print(f"Error writing to file '{output_path}': {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/foundry/scripts/runlogs/2025.s.sol b/foundry/scripts/runlogs/2025.s.sol index 1cd56ec576..c79ddb4ba5 100644 --- a/foundry/scripts/runlogs/2025.s.sol +++ b/foundry/scripts/runlogs/2025.s.sol @@ -45,7 +45,7 @@ contract Runlogs_2025_08 is SetupMainnet { int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); int256 profit = vaultChange - supplyChange; - oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 1 ether); + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); console.log("-----"); console.log("Profit %18e: ", profit); @@ -67,10 +67,5 @@ contract Runlogs_2025_08 is SetupMainnet { "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter ); vm.stopBroadcast(); - - // Todo: fetch the JSON output from the run and get path dynamically - // Todo: migrate this in a modifier - string memory json = vm.readFile("foundry/broadcast/2025.s.sol/1/dry-run/run-latest.json"); - testTransformJson(json); } } diff --git a/foundry/scripts/utils/JsonWriter.sol b/foundry/scripts/utils/JsonWriter.sol deleted file mode 100644 index 6333214dd1..0000000000 --- a/foundry/scripts/utils/JsonWriter.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import { stdJson } from "forge-std/StdJson.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { console } from "forge-std/console.sol"; - -abstract contract JsonWriter { - using stdJson for string; - - Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - - function testTransformJson(string memory inputJson) public pure { - string memory chainId = stdJson.readString(inputJson, ".chain"); - uint256 timestamp = stdJson.readUint(inputJson, ".timestamp"); - - // Format métadonnées - string memory meta = string.concat( - "{", - '"name":"Transactions Batch",', - '"description":"",', - '"txBuilderVersion":"1.16.1",', - '"createdFromSafeAddress":"', - stdJson.readString(inputJson, ".transactions[0].transaction.from"), - '",', - '"createdFromOwnerAddress":""', - "}" - ); - - // Format des transactions - // Todo: handle multiple transactions dynamically - string memory tx0 = formatTx(inputJson, 0); - string memory tx1 = formatTx(inputJson, 1); - - string memory allTxs = string.concat("[", tx0, ",", tx1, "]"); - - // Final JSON - string memory finalJson = string.concat( - "{", - '"version":"1.0",', - '"chainId":"', - chainId, - '",', - '"createdAt":', - vm.toString(timestamp), - ",", - '"meta":', - meta, - ",", - '"transactions":', - allTxs, - "}" - ); - - console.log(finalJson); - } - - function formatTx(string memory json, uint256 index) internal pure returns (string memory) { - string memory prefix = string.concat(".transactions[", vm.toString(index), "].transaction"); - string memory to = stdJson.readString(json, string.concat(prefix, ".to")); - string memory input = stdJson.readString(json, string.concat(prefix, ".input")); - - return string.concat( - "{", - '"to":"', - to, - '",', - '"value":"0",', - '"data":"', - input, - '",', - '"contractMethod":null,', - '"contractInputsValues":null', - "}" - ); - } -} diff --git a/foundry/scripts/utils/Setup.sol b/foundry/scripts/utils/Setup.sol index c16ddd8153..b5fb3d470c 100644 --- a/foundry/scripts/utils/Setup.sol +++ b/foundry/scripts/utils/Setup.sol @@ -47,9 +47,7 @@ abstract contract SetupMainnet is JsonWriter, Test, Script { ICurveStableSwapNG public oethWethCurvePool = ICurveStableSwapNG(Mainnet.OETH_WETH_CURVE_POOL); function setUp() public { - // Todo: fetch url and block number dynamically - vm.createSelectFork( - "url", 23073300 - ); + // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block + vm.createSelectFork(vm.envString("MAINNET_PROVIDER_URL")); } } From 7ac20821e70bb6076ecb68f7f9c0fbec9de5fa55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 12 Aug 2025 10:06:18 +0200 Subject: [PATCH 03/34] refactor: remove JsonWriter inheritance from SetupMainnet contract --- foundry/scripts/utils/Setup.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/foundry/scripts/utils/Setup.sol b/foundry/scripts/utils/Setup.sol index b5fb3d470c..227cbdc7db 100644 --- a/foundry/scripts/utils/Setup.sol +++ b/foundry/scripts/utils/Setup.sol @@ -20,13 +20,12 @@ import { ICurveStableSwapNG } from "contracts/contracts/interfaces/ICurveStableS // Helpers import { Mainnet } from "./Addresses.sol"; -import { JsonWriter } from "../utils/JsonWriter.sol"; import { Script } from "forge-std/Script.sol"; import { Test } from "forge-std/Test.sol"; // Foundry -abstract contract SetupMainnet is JsonWriter, Test, Script { +abstract contract SetupMainnet is Test, Script { // Governance address public strategist = Mainnet.MULTICHAIN_STRATEGIST; From 8e9ea4a0f795e72fcf49bc45a3544c75561575e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 12 Aug 2025 13:15:24 +0200 Subject: [PATCH 04/34] feat: add display option for JSON output and implement Runlogs_2025_08 contract --- foundry/helpers/broadcast_convertor.py | 11 ++++- .../runlogs/{2025.s.sol => 2025_08.s.sol} | 3 ++ foundry/scripts/runlogs/result.json | 28 ------------- foundry/scripts/runlogs/test.json | 42 ------------------- 4 files changed, 13 insertions(+), 71 deletions(-) rename foundry/scripts/runlogs/{2025.s.sol => 2025_08.s.sol} (92%) delete mode 100644 foundry/scripts/runlogs/result.json delete mode 100644 foundry/scripts/runlogs/test.json diff --git a/foundry/helpers/broadcast_convertor.py b/foundry/helpers/broadcast_convertor.py index d838d18687..069104cbdf 100644 --- a/foundry/helpers/broadcast_convertor.py +++ b/foundry/helpers/broadcast_convertor.py @@ -20,6 +20,11 @@ def parse_arguments(): default='safe', help='Suffix to add to filename (default: safe)' ) + parser.add_argument( + '--display', '-d', + default=False, + help='Display the output JSON in the console' + ) return parser.parse_args() def load_input_json(file_path): @@ -92,7 +97,11 @@ def main(): # Transform JSON result = transform_json(input_data) - + + # Print the output JSON + if args.display: + print(json.dumps(result, indent=2)) + # Generate output path and save output_path = get_output_path(args.input, args.suffix, args.output) try: diff --git a/foundry/scripts/runlogs/2025.s.sol b/foundry/scripts/runlogs/2025_08.s.sol similarity index 92% rename from foundry/scripts/runlogs/2025.s.sol rename to foundry/scripts/runlogs/2025_08.s.sol index c79ddb4ba5..ddf55c290c 100644 --- a/foundry/scripts/runlogs/2025.s.sol +++ b/foundry/scripts/runlogs/2025_08.s.sol @@ -12,6 +12,9 @@ contract Runlogs_2025_08 is SetupMainnet { _2025_08_04(); } + // ------------------------------------------------------------------ + // July 28, 2025 - Withdraw 2335 WETH from new Curve AMO + // ------------------------------------------------------------------ function _2025_08_04() internal { vm.startBroadcast(strategist); // Before diff --git a/foundry/scripts/runlogs/result.json b/foundry/scripts/runlogs/result.json deleted file mode 100644 index 6f8ee8c75a..0000000000 --- a/foundry/scripts/runlogs/result.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": "1.0", - "chainId": "1", - "createdAt": 1754400248404, - "meta": { - "name": "Transactions Batch", - "description": "", - "txBuilderVersion": "1.16.1", - "createdFromSafeAddress": "0x4ff1b9d9ba8558f5eafcec096318ea0d8b541971", - "createdFromOwnerAddress": "" - }, - "transactions": [ - { - "to": "0x39254033945aa2e4809cc2977e7087bee48bd7ab", - "value": "0", - "data": "0xaf14052c", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x31fd8618379d8e473ec2b1540b906e8e11d2a99b", - "value": "0", - "data": "0xb3d3d37e", - "contractMethod": null, - "contractInputsValues": null - } - ] -} \ No newline at end of file diff --git a/foundry/scripts/runlogs/test.json b/foundry/scripts/runlogs/test.json deleted file mode 100644 index ab1b042915..0000000000 --- a/foundry/scripts/runlogs/test.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "1.0", - "chainId": "1", - "createdAt": 1754386908, - "meta": { - "name": "Transactions Batch", - "description": "", - "txBuilderVersion": "1.16.1", - "createdFromSafeAddress": "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971", - "createdFromOwnerAddress": "" - }, - "transactions": [ - { - "to": "0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab", - "value": "0", - "data": "0xaf14052c", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x31FD8618379D8e473Ec2B1540B906E8e11D2A99b", - "value": "0", - "data": "0xb3d3d37e", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab", - "value": "0", - "data": "0xae69f3cb000000000000000000000000ba0e352ab5c13861c26e4e773e7a833c3a223fe6000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001158e460913d00000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x31FD8618379D8e473Ec2B1540B906E8e11D2A99b", - "value": "0", - "data": "0xb1d79df5000000000000000000000000000000000000000000000000000647a8bcdb29740000000000000000000000000000000000000000000000000de0b6b3a7640000fffffffffffffffffffffffffffffffffffffffffffffffe049024756c12df8c0000000000000000000000000000000000000000000000000de0b6b3a7640000", - "contractMethod": null, - "contractInputsValues": null - } - ] -} \ No newline at end of file From ba3e72a8347c0a417246ba35d580e5b34ab61edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Sun, 17 Aug 2025 20:43:53 +0200 Subject: [PATCH 05/34] feat: add Runlogs_2025_08 contract and JSON transformation script; update .gitignore and foundry.toml paths --- .gitignore | 5 ++--- {foundry => contracts}/scripts/runlogs/2025_08.s.sol | 4 ++-- .../scripts/runlogs}/broadcast_convertor.py | 0 .../scripts/runlogs}/utils/Addresses.sol | 0 .../scripts => contracts/scripts/runlogs}/utils/Setup.sol | 3 +-- foundry.toml | 8 ++++---- 6 files changed, 9 insertions(+), 11 deletions(-) rename {foundry => contracts}/scripts/runlogs/2025_08.s.sol (98%) rename {foundry/helpers => contracts/scripts/runlogs}/broadcast_convertor.py (100%) rename {foundry/scripts => contracts/scripts/runlogs}/utils/Addresses.sol (100%) rename {foundry/scripts => contracts/scripts/runlogs}/utils/Setup.sol (96%) diff --git a/.gitignore b/.gitignore index 7ee6786cd1..3315b137ca 100644 --- a/.gitignore +++ b/.gitignore @@ -64,11 +64,10 @@ contracts/.localKeyValueStorage contracts/.localKeyValueStorage.mainnet contracts/.localKeyValueStorage.holesky contracts/scripts/defender-actions/dist/ +contracts/broadcast/ +contracts/out/ cache/ -foundry/broadcast/ -foundry/out/ dependencies/ -.env contracts/lib/defender-actions/dist/ diff --git a/foundry/scripts/runlogs/2025_08.s.sol b/contracts/scripts/runlogs/2025_08.s.sol similarity index 98% rename from foundry/scripts/runlogs/2025_08.s.sol rename to contracts/scripts/runlogs/2025_08.s.sol index ddf55c290c..7516c590a4 100644 --- a/foundry/scripts/runlogs/2025_08.s.sol +++ b/contracts/scripts/runlogs/2025_08.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; // Helpers -import { SetupMainnet } from "../utils/Setup.sol"; +import { SetupMainnet } from "./utils/Setup.sol"; // Foundry import { console } from "forge-std/console.sol"; @@ -71,4 +71,4 @@ contract Runlogs_2025_08 is SetupMainnet { ); vm.stopBroadcast(); } -} +} \ No newline at end of file diff --git a/foundry/helpers/broadcast_convertor.py b/contracts/scripts/runlogs/broadcast_convertor.py similarity index 100% rename from foundry/helpers/broadcast_convertor.py rename to contracts/scripts/runlogs/broadcast_convertor.py diff --git a/foundry/scripts/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol similarity index 100% rename from foundry/scripts/utils/Addresses.sol rename to contracts/scripts/runlogs/utils/Addresses.sol diff --git a/foundry/scripts/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol similarity index 96% rename from foundry/scripts/utils/Setup.sol rename to contracts/scripts/runlogs/utils/Setup.sol index 227cbdc7db..5cfcf540c9 100644 --- a/foundry/scripts/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; - // Contracts - OUSD import { OUSD } from "contracts/contracts/token/OUSD.sol"; @@ -47,6 +46,6 @@ abstract contract SetupMainnet is Test, Script { function setUp() public { // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block - vm.createSelectFork(vm.envString("MAINNET_PROVIDER_URL")); + vm.createSelectFork(vm.envString("PROVIDER_URL")); } } diff --git a/foundry.toml b/foundry.toml index 2abb06d1c7..354e53287c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,9 +1,9 @@ [profile.default] src = "contracts/contracts" -out = "foundry/out" -script = "foundry/scripts" -libs = ["dependencies"] -broadcast = "foundry/broadcast" +out = "contracts/out" +script = "contracts/scripts" +libs = ["dependencies", "contracts/node_modules"] +broadcast = "contracts/broadcast" verbosity = 3 auto_detect_remappings = false viaIR = true From 38b9974f432c9bafa0dc6eda5b389336c2566e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 18 Aug 2025 08:31:03 +0200 Subject: [PATCH 06/34] fix: rename contract to Runlogs_2025_08_Mainnet for clarity --- contracts/scripts/runlogs/2025_08.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/scripts/runlogs/2025_08.s.sol b/contracts/scripts/runlogs/2025_08.s.sol index 7516c590a4..f596909b67 100644 --- a/contracts/scripts/runlogs/2025_08.s.sol +++ b/contracts/scripts/runlogs/2025_08.s.sol @@ -7,7 +7,7 @@ import { SetupMainnet } from "./utils/Setup.sol"; // Foundry import { console } from "forge-std/console.sol"; -contract Runlogs_2025_08 is SetupMainnet { +contract Runlogs_2025_08_Mainnet is SetupMainnet { function run() public { _2025_08_04(); } From cfcb4affc23f48624e85c17156255ec1320fcb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:58:46 +0200 Subject: [PATCH 07/34] Create README.md --- contracts/scripts/runlogs/README.md | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 contracts/scripts/runlogs/README.md diff --git a/contracts/scripts/runlogs/README.md b/contracts/scripts/runlogs/README.md new file mode 100644 index 0000000000..6b8d77226f --- /dev/null +++ b/contracts/scripts/runlogs/README.md @@ -0,0 +1,43 @@ +# How to use foundry runlogs? + +## 1. Ensure all dependencies are installed +At the root of the repo run: +```bash +forge soldeer install +cd contract +yarn install +``` + +## 2. Execute runlogs +In the `contracts` folder run: +```bash +forge script Runlogs_2025_08_Mainnet +``` +> Note: adjust the year, month and chain accordingly. + +This generates 2 files (that are `broadcast-ready` for execution) under `contracts/broadcast`: +- run-latest.json +- run-1755456667783.json (with the timestamp corresponding to the execution time) + +## 3. Convert runlogs into Safe-compatible JSON +Since these transactions are meant to be executed from the Safe, it is not possible to use `cast`. + +To convert a `broadcast-ready` into a Safe-compatible JSON file, use the script: `scripts/runlogs/broadcast_convertor.py`. + +In the `contracts` folder run: +```bash +python3 scripts/runlogs/broadcast_convertor.py -i broadcast/2025_08.s.sol/1/dry-run/run-latest.json +``` +> Note adjust the input accordingly. + +This creates, by default, a file named `run-latest-safe.json` in the same location as the input file, ready to be imported into the Safe UI. + + +## 4. Options for `broadcast_convertor.py` +``` + -h, --help show this help message and exit + --input, -i INPUT Path to input JSON file (MANDATORY) + --output, -o OUTPUT Path to output JSON file (default: adds -safe to input filename) + --suffix SUFFIX Suffix to add to filename (default: safe) + --display, -d DISPLAY Display the output JSON in the console +``` From 6cea131b0ba5fcaa0b08390c054cf8dfa1667703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 18 Aug 2025 09:45:59 +0200 Subject: [PATCH 08/34] feat: add Makefile for script execution and chain ID retrieval; update broadcast_convertor.py to display JSON output --- contracts/Makefile | 34 +++++++++++++++++++ .../scripts/runlogs/broadcast_convertor.py | 1 + 2 files changed, 35 insertions(+) create mode 100644 contracts/Makefile diff --git a/contracts/Makefile b/contracts/Makefile new file mode 100644 index 0000000000..d3d3660e1f --- /dev/null +++ b/contracts/Makefile @@ -0,0 +1,34 @@ +-include .env + +.EXPORT_ALL_VARIABLES: +MAKEFLAGS += --no-print-directory + +DATE ?= $(shell date +%Y_%m) +CHAIN ?= Mainnet + +VALID_CHAINS = Mainnet Base Sonic Plume Arbitrum Holesky +MAINNET_ID = 1 +BASE_ID = 8453 +SONIC_ID = 146 +PLUME_ID = 98865 +ARBITRUM_ID = 42161 +HOLESKY_ID = 17000 + +define get_chain_id +$(if $(filter $(CHAIN),$(VALID_CHAINS)),\ +$(if $(filter Mainnet,$(CHAIN)),$(MAINNET_ID),\ +$(if $(filter Base,$(CHAIN)),$(BASE_ID),\ +$(if $(filter Sonic,$(CHAIN)),$(SONIC_ID),\ +$(if $(filter Plume,$(CHAIN)),$(PLUME_ID),\ +$(if $(filter Arbitrum,$(CHAIN)),$(ARBITRUM_ID),\ +$(HOLESKY_ID)))))),\ +$(error CHAIN "$(CHAIN)" invalide. Chaînes valides: $(VALID_CHAINS))) +endef + +script: + $(eval CHAIN_ID := $(call get_chain_id)) + @echo "Running script RunlogsDATE=$(DATE)_CHAIN=$(CHAIN) \n" + forge script Runlogs_$(DATE)_$(CHAIN) + python3 scripts/runlogs/broadcast_convertor.py -i broadcast/$(DATE).s.sol/$(CHAIN_ID)/dry-run/run-latest.json -d True + +.PHONY: script \ No newline at end of file diff --git a/contracts/scripts/runlogs/broadcast_convertor.py b/contracts/scripts/runlogs/broadcast_convertor.py index 069104cbdf..ab42a90bda 100644 --- a/contracts/scripts/runlogs/broadcast_convertor.py +++ b/contracts/scripts/runlogs/broadcast_convertor.py @@ -100,6 +100,7 @@ def main(): # Print the output JSON if args.display: + print("\nJSON for Safe:") print(json.dumps(result, indent=2)) # Generate output path and save From ae5369a269eab7eb3ca3a024c10844a9ac443083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:50:11 +0200 Subject: [PATCH 09/34] Update README.md --- contracts/scripts/runlogs/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/scripts/runlogs/README.md b/contracts/scripts/runlogs/README.md index 6b8d77226f..76ce2566e1 100644 --- a/contracts/scripts/runlogs/README.md +++ b/contracts/scripts/runlogs/README.md @@ -41,3 +41,15 @@ This creates, by default, a file named `run-latest-safe.json` in the same locati --suffix SUFFIX Suffix to add to filename (default: safe) --display, -d DISPLAY Display the output JSON in the console ``` + +## 5. How generates Safe JSON in just one command? +In the `contracts` folder: +```makefile +make script +or +make script CHAIN=Mainnet +or +make script DATE=2025_08 CHAIN=Mainnet +``` + +By default it take the current year+month to fetch which runlogs to run. The default chain is Mainnet. From 031e3fc1b69bde0b3309e8b5df7ed87bb825186a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 18 Aug 2025 10:53:36 +0200 Subject: [PATCH 10/34] fix: correct error message for invalid chain in Makefile --- contracts/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Makefile b/contracts/Makefile index d3d3660e1f..ac4fc227a0 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -22,7 +22,7 @@ $(if $(filter Sonic,$(CHAIN)),$(SONIC_ID),\ $(if $(filter Plume,$(CHAIN)),$(PLUME_ID),\ $(if $(filter Arbitrum,$(CHAIN)),$(ARBITRUM_ID),\ $(HOLESKY_ID)))))),\ -$(error CHAIN "$(CHAIN)" invalide. Chaînes valides: $(VALID_CHAINS))) +$(error CHAIN "$(CHAIN)" is not valid. Valid chains are: $(VALID_CHAINS))) endef script: From 3fe2efaf9efc9ebcb4da28ac7028796eb121da4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 18 Aug 2025 15:30:22 +0200 Subject: [PATCH 11/34] feat: refactor contract setup and add Base chain support; enhance logging in runlogs --- contracts/scripts/runlogs/2025_08.s.sol | 77 +++++++++++++++++-- contracts/scripts/runlogs/utils/Addresses.sol | 29 ++++++- contracts/scripts/runlogs/utils/Setup.sol | 37 ++++++++- 3 files changed, 133 insertions(+), 10 deletions(-) diff --git a/contracts/scripts/runlogs/2025_08.s.sol b/contracts/scripts/runlogs/2025_08.s.sol index f596909b67..7c9352279e 100644 --- a/contracts/scripts/runlogs/2025_08.s.sol +++ b/contracts/scripts/runlogs/2025_08.s.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -// Helpers +// Setup +import { SetupBase } from "./utils/Setup.sol"; import { SetupMainnet } from "./utils/Setup.sol"; // Foundry @@ -51,9 +52,9 @@ contract Runlogs_2025_08_Mainnet is SetupMainnet { oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); console.log("-----"); - console.log("Profit %18e: ", profit); - console.log("OETH Supply change %18e: ", supplyChange); - console.log("Vault value change %18e: ", vaultChange); + console.log("Profit : %18e", profit); + console.log("OETH Supply change: %18e", supplyChange); + console.log("Vault value change: %18e", vaultChange); // AMO pool after uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); @@ -71,4 +72,70 @@ contract Runlogs_2025_08_Mainnet is SetupMainnet { ); vm.stopBroadcast(); } -} \ No newline at end of file +} + +contract Runlogs_2025_08_Base is SetupBase { + function run() public { + _2025_08_18(); + } + + // ------------------------------------------------------------------ + // August 18, 2025 - Deposit 90 WETH on Curve AMO + // ------------------------------------------------------------------ + function _2025_08_18() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToDeposit = 90 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToDeposit; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); + + console.log("-----"); + console.log("Profit : %18e ", profit); + console.log("OETH Supply change: %18e ", supplyChange); + console.log("Vault value change: %18e ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + } +} diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index f03663d8cf..2307c6492b 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -library Mainnet { +library CrossChain { // Governance - address public constant MULTICHAIN_STRATEGIST = 0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971; - + address public constant STRATEGIST = 0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971; +} + +library Mainnet { + uint256 public constant CHAIN_ID = 1; // Mainnet chain ID // OUSD address public constant OUSD = 0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86; @@ -24,3 +27,23 @@ library Mainnet { // Curve pools address public constant OETH_WETH_CURVE_POOL = 0xcc7d5785AD5755B6164e21495E07aDb0Ff11C2A8; } + +library Base { + uint256 public constant CHAIN_ID = 5483; // Base chain ID + + // OETHb + address public constant OETHB = 0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3; + address public constant WOETHB = 0x7FcD174E80f264448ebeE8c88a7C4476AAF58Ea6; + address public constant OETHB_VAULT = 0x98a0CbeF61bD2D21435f433bE4CD42B56B38CC93; + address public constant OETHB_VAULT_VALUE_CHECKER = 0x9D98Cf85B65Fa1ACef5e9AAA2300753aDF7bcf6A; + + // OETHb Strategies + address public constant OETHB_WETH_CURVE_AMO = 0x9cfcAF81600155e01c63e4D2993A8A81A8205829; + address public constant OETHB_WETH_AERODROME_POOL = 0xF611cC500eEE7E4e4763A05FE623E2363c86d2Af; + + // Other token + address public constant WETH = 0x4200000000000000000000000000000000000006; + + // Curve pools + address public constant OETHB_WETH_CURVE_POOL = 0x302A94E3C28c290EAF2a4605FC52e11Eb915f378; +} diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index 5cfcf540c9..892d2e60d5 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -1,24 +1,32 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; // Contracts - OUSD + import { OUSD } from "contracts/contracts/token/OUSD.sol"; // Contracts - OETH import { OETH } from "contracts/contracts/token/OETH.sol"; import { WOETH } from "contracts/contracts/token/WOETH.sol"; +import { OETHBase } from "contracts/contracts/token/OETHBase.sol"; +import { WOETHBase } from "contracts/contracts/token/WOETHBase.sol"; import { OETHVaultCore } from "contracts/contracts/vault/OETHVaultCore.sol"; import { OETHVaultAdmin } from "contracts/contracts/vault/OETHVaultAdmin.sol"; +import { OETHBaseVaultCore } from "contracts/contracts/vault/OETHBaseVaultCore.sol"; +import { OETHBaseVaultAdmin } from "contracts/contracts/vault/OETHBaseVaultAdmin.sol"; import { OETHVaultValueChecker } from "contracts/contracts/strategies/VaultValueChecker.sol"; // Contracts - Strategies import { CurveAMOStrategy } from "contracts/contracts/strategies/CurveAMOStrategy.sol"; +import { BaseCurveAMOStrategy } from "contracts/contracts/strategies/BaseCurveAMOStrategy.sol"; +import { AerodromeAMOStrategy } from + "contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol"; // Interfaces import { IWETH9 } from "contracts/contracts/interfaces/IWETH9.sol"; import { ICurveStableSwapNG } from "contracts/contracts/interfaces/ICurveStableSwapNG.sol"; // Helpers -import { Mainnet } from "./Addresses.sol"; +import { CrossChain, Mainnet, Base } from "./Addresses.sol"; import { Script } from "forge-std/Script.sol"; import { Test } from "forge-std/Test.sol"; @@ -26,7 +34,7 @@ import { Test } from "forge-std/Test.sol"; abstract contract SetupMainnet is Test, Script { // Governance - address public strategist = Mainnet.MULTICHAIN_STRATEGIST; + address public strategist = CrossChain.STRATEGIST; // OUSD OUSD public ousd = OUSD(Mainnet.OUSD); @@ -49,3 +57,28 @@ abstract contract SetupMainnet is Test, Script { vm.createSelectFork(vm.envString("PROVIDER_URL")); } } + +abstract contract SetupBase is Test, Script { + // Governance + address public strategist = CrossChain.STRATEGIST; + + // OETH + OETHBase public oeth = OETHBase(Base.OETHB); + WOETHBase public woeth = WOETHBase(Base.WOETHB); + OETHBaseVaultCore public oethVaultCore = OETHBaseVaultCore(Base.OETHB_VAULT); + OETHBaseVaultAdmin public oethVaultAdmin = OETHBaseVaultAdmin(Base.OETHB_VAULT); + BaseCurveAMOStrategy public oethWethCurveAMO = BaseCurveAMOStrategy(Base.OETHB_WETH_CURVE_AMO); + AerodromeAMOStrategy public oethWethAerodromeAMO = + AerodromeAMOStrategy(Base.OETHB_WETH_AERODROME_POOL); + OETHVaultValueChecker public oethVaultValueChecker = + OETHVaultValueChecker(Base.OETHB_VAULT_VALUE_CHECKER); + + // Interfaces + IWETH9 public weth = IWETH9(Base.WETH); + ICurveStableSwapNG public oethWethCurvePool = ICurveStableSwapNG(Base.OETHB_WETH_CURVE_POOL); + + function setUp() public { + // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block + vm.createSelectFork(vm.envString("BASE_PROVIDER_URL")); + } +} From 072fea512b037e19739a184fa4f9ad864a1840f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 21 Aug 2025 20:20:17 +0200 Subject: [PATCH 12/34] feat: add function to deposit 125 WETH on Curve AMO; update run method in Runlogs_2025_08_Base --- contracts/scripts/runlogs/2025_08.s.sol | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/contracts/scripts/runlogs/2025_08.s.sol b/contracts/scripts/runlogs/2025_08.s.sol index 7c9352279e..f97657cf75 100644 --- a/contracts/scripts/runlogs/2025_08.s.sol +++ b/contracts/scripts/runlogs/2025_08.s.sol @@ -77,6 +77,7 @@ contract Runlogs_2025_08_Mainnet is SetupMainnet { contract Runlogs_2025_08_Base is SetupBase { function run() public { _2025_08_18(); + _2025_08_21(); } // ------------------------------------------------------------------ @@ -138,4 +139,64 @@ contract Runlogs_2025_08_Base is SetupBase { ); vm.stopBroadcast(); } + + // ------------------------------------------------------------------ + // August 21, 2025 - Deposit 125 WETH on Curve AMO + // ------------------------------------------------------------------ + function _2025_08_21() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToDeposit = 125 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToDeposit; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); + + console.log("-----"); + console.log("Profit : %18e ", profit); + console.log("OETH Supply change: %18e ", supplyChange); + console.log("Vault value change: %18e ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + } } From 4a9bda9cf129e83df25771eb402432c3473b2e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 28 Aug 2025 16:53:45 +0200 Subject: [PATCH 13/34] migrate from python script to foundry script --- contracts/Makefile | 27 +++- .../scripts/runlogs/broadcast_convertor.py | 117 ----------------- .../runlogs/utils/BroadcastConvertor.sol | 123 ++++++++++++++++++ foundry.toml | 2 +- 4 files changed, 149 insertions(+), 120 deletions(-) delete mode 100644 contracts/scripts/runlogs/broadcast_convertor.py create mode 100644 contracts/scripts/runlogs/utils/BroadcastConvertor.sol diff --git a/contracts/Makefile b/contracts/Makefile index ac4fc227a0..6d173be614 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -3,6 +3,28 @@ .EXPORT_ALL_VARIABLES: MAKEFLAGS += --no-print-directory +# ANSI Colors +RESET=\033[0m + +BLACK=\033[0;30m +RED=\033[0;31m +GREEN=\033[0;32m +YELLOW=\033[0;33m +BLUE=\033[0;34m +MAGENTA=\033[0;35m +CYAN=\033[0;36m +WHITE=\033[0;37m + +# Bold colors +BOLD_BLACK=\033[1;30m +BOLD_RED=\033[1;31m +BOLD_GREEN=\033[1;32m +BOLD_YELLOW=\033[1;33m +BOLD_BLUE=\033[1;34m +BOLD_MAGENTA=\033[1;35m +BOLD_CYAN=\033[1;36m +BOLD_WHITE=\033[1;37m + DATE ?= $(shell date +%Y_%m) CHAIN ?= Mainnet @@ -27,8 +49,9 @@ endef script: $(eval CHAIN_ID := $(call get_chain_id)) - @echo "Running script RunlogsDATE=$(DATE)_CHAIN=$(CHAIN) \n" + @echo "$(BOLD_BLUE)Running script RunlogsDATE=$(DATE)_CHAIN=$(CHAIN) $(RESET) \n" forge script Runlogs_$(DATE)_$(CHAIN) - python3 scripts/runlogs/broadcast_convertor.py -i broadcast/$(DATE).s.sol/$(CHAIN_ID)/dry-run/run-latest.json -d True + @echo "$(BOLD_BLUE)\nConverting foundry broadcast messages to Safe format... $(RESET) \n" + forge script BroadcastConvertor contracts/broadcast/$(DATE).s.sol/$(CHAIN_ID)/dry-run/ .PHONY: script \ No newline at end of file diff --git a/contracts/scripts/runlogs/broadcast_convertor.py b/contracts/scripts/runlogs/broadcast_convertor.py deleted file mode 100644 index ab42a90bda..0000000000 --- a/contracts/scripts/runlogs/broadcast_convertor.py +++ /dev/null @@ -1,117 +0,0 @@ -import json -import sys -import os -import argparse - -def parse_arguments(): - """Parse command line arguments""" - parser = argparse.ArgumentParser(description='Transform JSON format for Safe transactions') - parser.add_argument( - '--input', '-i', - required=True, - help='Path to input JSON file' - ) - parser.add_argument( - '--output', '-o', - help='Path to output JSON file (default: adds -safe to input filename)' - ) - parser.add_argument( - '--suffix', - default='safe', - help='Suffix to add to filename (default: safe)' - ) - parser.add_argument( - '--display', '-d', - default=False, - help='Display the output JSON in the console' - ) - return parser.parse_args() - -def load_input_json(file_path): - """Load JSON from file""" - try: - with open(file_path, 'r') as f: - return json.load(f) - except FileNotFoundError: - print(f"Error: File '{file_path}' not found") - sys.exit(1) - except json.JSONDecodeError as e: - print(f"Error: Invalid JSON in file '{file_path}': {e}") - sys.exit(1) - -def transform_json(input_data): - """Transform input JSON to target format""" - # Create output structure - output_json = { - "version": "1.0", - "chainId": str(input_data["chain"]), - "createdAt": input_data["timestamp"] // 1000, # Convert ms to seconds - "meta": { - "name": "Transactions Batch", - "description": "", - "txBuilderVersion": "1.16.1", - "createdFromSafeAddress": "", - "createdFromOwnerAddress": "" - }, - "transactions": [] - } - - # Extract Safe address from first transaction (from field) - if input_data["transactions"]: - safe_address = input_data["transactions"][0]["transaction"]["from"] - output_json["meta"]["createdFromSafeAddress"] = safe_address - - # Transform each transaction - for tx in input_data["transactions"]: - transaction = tx["transaction"] - - # Convert hex value to integer then to string - value = str(int(transaction["value"], 16)) - - transformed_tx = { - "to": transaction["to"], - "value": value, - "data": transaction["input"], - "contractMethod": None, - "contractInputsValues": None - } - - output_json["transactions"].append(transformed_tx) - - return output_json - -def get_output_path(input_path, suffix, custom_output=None): - """Generate output path by adding suffix before file extension""" - if custom_output: - return custom_output - - name, ext = os.path.splitext(input_path) - return f"{name}-{suffix}{ext}" - -def main(): - """Main function""" - args = parse_arguments() - - # Load input JSON - input_data = load_input_json(args.input) - - # Transform JSON - result = transform_json(input_data) - - # Print the output JSON - if args.display: - print("\nJSON for Safe:") - print(json.dumps(result, indent=2)) - - # Generate output path and save - output_path = get_output_path(args.input, args.suffix, args.output) - try: - with open(output_path, 'w') as f: - json.dump(result, f, indent=2) - print(f"Successfully transformed and saved to: {output_path}") - except Exception as e: - print(f"Error writing to file '{output_path}': {e}") - sys.exit(1) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/contracts/scripts/runlogs/utils/BroadcastConvertor.sol b/contracts/scripts/runlogs/utils/BroadcastConvertor.sol new file mode 100644 index 0000000000..6d2df07529 --- /dev/null +++ b/contracts/scripts/runlogs/utils/BroadcastConvertor.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { stdJson } from "forge-std/StdJson.sol"; + +// Helpers +import { Script } from "forge-std/Script.sol"; +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +/// @title Broadcast Convertor +/// @author Origin Protocol +/// @notice This contract is responsible for converting foundry broadcast messages into a format suitable for the Safe. +contract BroadcastConvertor is Script, Test { + using stdJson for string; + + struct Json { + uint256 chain; + string commit; // git commit hash + string libraries; // empty + string pending; // empty + string receipts; // empty + uint256 timestamp; + Transactions[] transactions; + Returns returnData; // empty + } + + struct Transactions { + string additionalContracts; // empty + uint256 arguments; // null + address contractAddress; + string contractName; + uint256 functions; // null + uint256 hash; // null + bool isFixedGasLimit; + Transaction transaction; + string transactionType; + } + + struct Transaction { + bytes chainId; + address from; + bytes gas; + bytes input; + bytes nonce; + address to; + bytes value; + } + + struct Returns { + uint256 empty; + } + + function run(string memory path) external { + // Fetch JSON + string memory inputJson = vm.readFile(string.concat(path, "run-latest.json")); + + // Convert Json into Struct + Json memory json = abi.decode(vm.parseJson(inputJson), (Json)); + + // Prepare for Safe + string memory safeJson = _prepareForSafe(json); + + console.log("Use this JSON is Safe:\n%s", safeJson); + + // Write JSON + vm.writeJson(safeJson, string.concat(path, "run-latest-safe.json")); + } + + function _prepareForSafe(Json memory json) internal pure returns (string memory) { + // Header + string memory header = string.concat( + '{ "version": "1.0", "chainId": "', + vm.toString(json.chain), + '", "createdAt": ', + vm.toString(json.timestamp / 1000), // to convert milliseconds to seconds + ", " + ); + + // Meta + string memory meta = string.concat( + '"meta": { "name": "Transaction Batch", "description": "", "txBuilderVersion": "1.16.1", "createdFromSafeAddress": "', + vm.toString(json.transactions[0].transaction.from), + '", "createdFromOwnerAddress": ""},' + ); + + // Transactions + string memory transactions = '"transactions": [ '; + uint256 transactionsCount = json.transactions.length; + for (uint256 i; i < transactionsCount; i++) { + // Beginning of dictionnary + transactions = string.concat(transactions, "{ "); + // to + transactions = string.concat(transactions, '"to":'); + transactions = + string.concat(transactions, '"', vm.toString(json.transactions[i].transaction.to), '",'); + // value + transactions = string.concat(transactions, '"value":'); + transactions = + string.concat(transactions, '"', vm.toString(json.transactions[i].transaction.value), '",'); + // data + transactions = string.concat(transactions, '"data": '); + transactions = + string.concat(transactions, '"', vm.toString(json.transactions[i].transaction.input), '",'); + // contractMethod & contractInputsValues + transactions = + string.concat(transactions, '"contractMethod": null, "contractInputsValues": null'); + // End of dictionary + transactions = string.concat(transactions, "}"); + + // Add comma separator if needed + if (i < transactionsCount - 1) { + transactions = string.concat(transactions, ", "); + } + } + transactions = string.concat(transactions, " ]"); + + // Building final JSON + string memory jsonObj = string.concat(header, meta, transactions, " }"); + + return jsonObj; + } +} diff --git a/foundry.toml b/foundry.toml index 354e53287c..1867a35677 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,7 +8,7 @@ verbosity = 3 auto_detect_remappings = false viaIR = true etherscan_api_key = "${ETHERSCAN_API_KEY}" -fs_permissions = [{ access = "read", path = "./"}] +fs_permissions = [{ access = "read-write", path = "./"}] remappings = [ "@openzeppelin/=contracts/node_modules/@openzeppelin", From d67242062b0428145a530c69d696c5b5bd459245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 28 Aug 2025 18:23:34 +0200 Subject: [PATCH 14/34] feat: enhance BroadcastConvertor to support timelock transactions and improve JSON handling --- .../runlogs/utils/BroadcastConvertor.sol | 121 +++++++++++++++--- 1 file changed, 105 insertions(+), 16 deletions(-) diff --git a/contracts/scripts/runlogs/utils/BroadcastConvertor.sol b/contracts/scripts/runlogs/utils/BroadcastConvertor.sol index 6d2df07529..023654083c 100644 --- a/contracts/scripts/runlogs/utils/BroadcastConvertor.sol +++ b/contracts/scripts/runlogs/utils/BroadcastConvertor.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +// Foundry +import { Test } from "forge-std/Test.sol"; +import { Script } from "forge-std/Script.sol"; +import { console } from "forge-std/console.sol"; import { stdJson } from "forge-std/StdJson.sol"; // Helpers -import { Script } from "forge-std/Script.sol"; -import { Test } from "forge-std/Test.sol"; -import { console } from "forge-std/console.sol"; +import { Mainnet, Base, Sonic } from "./Addresses.sol"; /// @title Broadcast Convertor /// @author Origin Protocol @@ -14,6 +16,7 @@ import { console } from "forge-std/console.sol"; contract BroadcastConvertor is Script, Test { using stdJson for string; + // ⚠️ Never change the structure of these JSONs, alphabetical order matters!! struct Json { uint256 chain; string commit; // git commit hash @@ -51,23 +54,28 @@ contract BroadcastConvertor is Script, Test { uint256 empty; } + /// @notice Main function to run the conversion from foundry broadcast messages to Safe format + /// @param path The path where the run-latest.json file is located function run(string memory path) external { // Fetch JSON string memory inputJson = vm.readFile(string.concat(path, "run-latest.json")); - // Convert Json into Struct + // Convert Json into Json-Struct Json memory json = abi.decode(vm.parseJson(inputJson), (Json)); // Prepare for Safe - string memory safeJson = _prepareForSafe(json); + string memory safeJson = prepareForSafe(json); + // Print Safe JSON console.log("Use this JSON is Safe:\n%s", safeJson); // Write JSON vm.writeJson(safeJson, string.concat(path, "run-latest-safe.json")); } - function _prepareForSafe(Json memory json) internal pure returns (string memory) { + /// @notice Prepares the JSON for Safe format + /// @param json The original JSON in Json-Struct format + function prepareForSafe(Json memory json) public pure returns (string memory) { // Header string memory header = string.concat( '{ "version": "1.0", "chainId": "', @@ -78,14 +86,32 @@ contract BroadcastConvertor is Script, Test { ); // Meta - string memory meta = string.concat( - '"meta": { "name": "Transaction Batch", "description": "", "txBuilderVersion": "1.16.1", "createdFromSafeAddress": "', - vm.toString(json.transactions[0].transaction.from), - '", "createdFromOwnerAddress": ""},' - ); + string memory meta = + '"meta": { "name": "Transaction Batch", "description": "", "txBuilderVersion": "1.16.1", "createdFromSafeAddress": "", "createdFromOwnerAddress": ""},'; + + // Fetch if the transaction is targeting a timelock or not + uint256 delay = timelockDelay(json); // Transactions - string memory transactions = '"transactions": [ '; + string memory transactions = string.concat( + '"transactions": [ ', + delay == 0 ? rawTransactions(json) : transactionForTimelock(json, delay), + " ]," + ); + + // Is it targeting a timelock + string memory isTargetingTimelock = + string.concat('"targetingTimelock": ', delay == 0 ? "false" : "true"); + + // Building final JSON + string memory jsonObj = string.concat(header, meta, transactions, isTargetingTimelock, " }"); + + return jsonObj; + } + + /// @notice Prepares transactions for Safe format + /// @param json The original JSON in Json-Struct format + function rawTransactions(Json memory json) public pure returns (string memory transactions) { uint256 transactionsCount = json.transactions.length; for (uint256 i; i < transactionsCount; i++) { // Beginning of dictionnary @@ -113,11 +139,74 @@ contract BroadcastConvertor is Script, Test { transactions = string.concat(transactions, ", "); } } - transactions = string.concat(transactions, " ]"); + } - // Building final JSON - string memory jsonObj = string.concat(header, meta, transactions, " }"); + /// @notice Prepares a transaction for Safe format, when a Timelock is targeted + /// @dev This function will batch all the tx in a single one, and call the timelock pranked on the runlog + /// @param json The original JSON in Json-Struct format + /// @param delay The delay for the timelock + function transactionForTimelock(Json memory json, uint256 delay) + public + pure + returns (string memory transaction) + { + uint256 len = json.transactions.length; + address[] memory targets = new address[](len); + uint256[] memory values = new uint256[](len); + bytes[] memory payloads = new bytes[](len); + + for (uint256 i = 0; i < len; i++) { + targets[i] = json.transactions[i].transaction.to; + values[i] = uint256(bytes32(json.transactions[i].transaction.value)); + payloads[i] = json.transactions[i].transaction.input; + } - return jsonObj; + // to + transaction = + string.concat(' { "to": "', vm.toString(json.transactions[0].transaction.from), '" '); + + // value + transaction = string.concat(transaction, ' , "value": "0" '); + + // data + transaction = string.concat( + transaction, + ' , "data": "', + vm.toString( + abi.encodeWithSelector( + Timelock.scheduleBatch.selector, targets, values, payloads, 0, 0, delay + ) + ), + '",' + ); + + // contractMethod & contractInputsValues + transaction = + string.concat(transaction, ' "contractMethod": null, "contractInputsValues": null }'); + } + + /// @notice Determine if the transaction is targeting a Timelock, if so return timelock delay + /// @dev This is hardcoded instead of dynamic fetching, this allow not forking blockchain and faster runtime + /// @param json The original JSON in Json-Struct format + /// @return delay The delay for the timelock, 0 if not found + function timelockDelay(Json memory json) public pure returns (uint256 delay) { + if (json.chain == 1 && json.transactions[0].transaction.from == Mainnet.TIMELOCK) { + delay = 2 days; + } else if (json.chain == 5483 && json.transactions[0].transaction.from == Base.TIMELOCK) { + delay = 2 days; + } else if (json.chain == 146 && json.transactions[0].transaction.from == Sonic.TIMELOCK) { + delay = 2 days; + } } } + +interface Timelock { + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata dataElements, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; +} From 1c3932705e38b330ef1b8aea4b42783e472a15c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 28 Aug 2025 18:23:48 +0200 Subject: [PATCH 15/34] feat: add support for Sonic chain --- contracts/scripts/runlogs/2025_08.s.sol | 86 +++++++++++++++++++ contracts/scripts/runlogs/utils/Addresses.sol | 18 ++++ contracts/scripts/runlogs/utils/Setup.sol | 14 ++- 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/contracts/scripts/runlogs/2025_08.s.sol b/contracts/scripts/runlogs/2025_08.s.sol index f97657cf75..b3ef7b41a9 100644 --- a/contracts/scripts/runlogs/2025_08.s.sol +++ b/contracts/scripts/runlogs/2025_08.s.sol @@ -3,8 +3,11 @@ pragma solidity ^0.8.0; // Setup import { SetupBase } from "./utils/Setup.sol"; +import { SetupSonic } from "./utils/Setup.sol"; import { SetupMainnet } from "./utils/Setup.sol"; +import { Sonic } from "./utils/Addresses.sol"; + // Foundry import { console } from "forge-std/console.sol"; @@ -78,6 +81,7 @@ contract Runlogs_2025_08_Base is SetupBase { function run() public { _2025_08_18(); _2025_08_21(); + _2025_08_28(); } // ------------------------------------------------------------------ @@ -199,4 +203,86 @@ contract Runlogs_2025_08_Base is SetupBase { ); vm.stopBroadcast(); } + + // ------------------------------------------------------------------ + // August 28, 2025 - Deposit 85 WETH on Curve AMO + // ------------------------------------------------------------------ + function _2025_08_28() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToDeposit = 85 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToDeposit; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 1 ether); + + console.log("-----"); + console.log("Profit : %18e ", profit); + console.log("OETH Supply change: %18e ", supplyChange); + console.log("Vault value change: %18e ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + } +} + +contract Runlogs_2025_08_Sonic is SetupSonic { + function run() public { + _2025_08_28(); + } + + function _2025_08_28() internal { + vm.startBroadcast(timelock); + // Before + + // Main action + (bool success,) = Sonic.ARM.call( + abi.encodeWithSignature( + "setFeeCollector(address)", 0xBB077E716A5f1F1B63ed5244eBFf5214E50fec8c + ) + ); + require(success, "Failed to set fee collector"); + + // After + vm.stopBroadcast(); + } } diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index 2307c6492b..68c936db86 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -8,6 +8,10 @@ library CrossChain { library Mainnet { uint256 public constant CHAIN_ID = 1; // Mainnet chain ID + + // Governance + address public constant TIMELOCK = 0x35918cDE7233F2dD33fA41ae3Cb6aE0e42E0e69F; + // OUSD address public constant OUSD = 0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86; @@ -31,6 +35,9 @@ library Mainnet { library Base { uint256 public constant CHAIN_ID = 5483; // Base chain ID + // Governance + address public constant TIMELOCK = 0xf817cb3092179083c48c014688D98B72fB61464f; + // OETHb address public constant OETHB = 0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3; address public constant WOETHB = 0x7FcD174E80f264448ebeE8c88a7C4476AAF58Ea6; @@ -47,3 +54,14 @@ library Base { // Curve pools address public constant OETHB_WETH_CURVE_POOL = 0x302A94E3C28c290EAF2a4605FC52e11Eb915f378; } + +library Sonic { + uint256 public constant CHAIN_ID = 146; // Sonic chain ID + + // Governance + address public constant GOVERNOR = 0xAdDEA7933Db7d83855786EB43a238111C69B00b6; + address public constant TIMELOCK = 0x31a91336414d3B955E494E7d485a6B06b55FC8fB; + + // ARM + address public constant ARM = 0x2F872623d1E1Af5835b08b0E49aAd2d81d649D30; +} diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index 892d2e60d5..3c7ffb9cab 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -26,7 +26,7 @@ import { IWETH9 } from "contracts/contracts/interfaces/IWETH9.sol"; import { ICurveStableSwapNG } from "contracts/contracts/interfaces/ICurveStableSwapNG.sol"; // Helpers -import { CrossChain, Mainnet, Base } from "./Addresses.sol"; +import { CrossChain, Mainnet, Base, Sonic } from "./Addresses.sol"; import { Script } from "forge-std/Script.sol"; import { Test } from "forge-std/Test.sol"; @@ -82,3 +82,15 @@ abstract contract SetupBase is Test, Script { vm.createSelectFork(vm.envString("BASE_PROVIDER_URL")); } } + +abstract contract SetupSonic is Test, Script { + // Governance + address public governor = Sonic.GOVERNOR; + address public timelock = Sonic.TIMELOCK; + address public strategist = CrossChain.STRATEGIST; + + function setUp() public { + // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block + vm.createSelectFork(vm.envString("SONIC_PROVIDER_URL")); + } +} From 0f914ca202548ba804dd2fe6504ed693b929d505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 29 Aug 2025 12:59:01 +0200 Subject: [PATCH 16/34] feat: add ISonicARM interface and update runlogs for Sonic integration; include OS address in Addresses --- .../contracts/interfaces/arm/ISonicARM.sol | 144 ++++++++++++++++++ contracts/scripts/runlogs/2025_08.s.sol | 14 +- contracts/scripts/runlogs/utils/Addresses.sol | 3 + contracts/scripts/runlogs/utils/Setup.sol | 18 ++- 4 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 contracts/contracts/interfaces/arm/ISonicARM.sol diff --git a/contracts/contracts/interfaces/arm/ISonicARM.sol b/contracts/contracts/interfaces/arm/ISonicARM.sol new file mode 100644 index 0000000000..9c4755d1a3 --- /dev/null +++ b/contracts/contracts/interfaces/arm/ISonicARM.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface ISonicARM { + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + error ERC20InvalidApprover(address approver); + error ERC20InvalidReceiver(address receiver); + error ERC20InvalidSender(address sender); + error ERC20InvalidSpender(address spender); + error InvalidInitialization(); + error NotInitializing(); + error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); + error SafeCastOverflowedIntToUint(int256 value); + error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); + error SafeCastOverflowedUintToInt(uint256 value); + + event ARMBufferUpdated(uint256 armBuffer); + event ActiveMarketUpdated(address indexed market); + event AdminChanged(address previousAdmin, address newAdmin); + event Allocated(address indexed market, int256 assets); + event Approval(address indexed owner, address indexed spender, uint256 value); + event CapManagerUpdated(address indexed capManager); + event ClaimOriginWithdrawals(uint256[] requestIds, uint256 amountClaimed); + event CrossPriceUpdated(uint256 crossPrice); + event Deposit(address indexed owner, uint256 assets, uint256 shares); + event FeeCollected(address indexed feeCollector, uint256 fee); + event FeeCollectorUpdated(address indexed newFeeCollector); + event FeeUpdated(uint256 fee); + event Initialized(uint64 version); + event MarketAdded(address indexed market); + event MarketRemoved(address indexed market); + event OperatorChanged(address newAdmin); + event RedeemClaimed(address indexed withdrawer, uint256 indexed requestId, uint256 assets); + event RedeemRequested( + address indexed withdrawer, uint256 indexed requestId, uint256 assets, uint256 queued, uint256 claimTimestamp + ); + event RequestOriginWithdrawal(uint256 amount, uint256 requestId); + event TraderateChanged(uint256 traderate0, uint256 traderate1); + event Transfer(address indexed from, address indexed to, uint256 value); + + function FEE_SCALE() external view returns (uint256); + function MAX_CROSS_PRICE_DEVIATION() external view returns (uint256); + function PRICE_SCALE() external view returns (uint256); + function activeMarket() external view returns (address); + function addMarkets(address[] memory _markets) external; + function allocate() external returns (int256 liquidityDelta); + function allocateThreshold() external view returns (int256); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function armBuffer() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function baseAsset() external view returns (address); + function capManager() external view returns (address); + function claimDelay() external view returns (uint256); + function claimOriginWithdrawals(uint256[] memory requestIds) external returns (uint256 amountClaimed); + function claimRedeem(uint256 requestId) external returns (uint256 assets); + function claimable() external view returns (uint256 claimableAmount); + function collectFees() external returns (uint256 fees); + function convertToAssets(uint256 shares) external view returns (uint256 assets); + function convertToShares(uint256 assets) external view returns (uint256 shares); + function crossPrice() external view returns (uint256); + function decimals() external view returns (uint8); + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + function deposit(uint256 assets) external returns (uint256 shares); + function fee() external view returns (uint16); + function feeCollector() external view returns (address); + function feesAccrued() external view returns (uint256 fees); + function initialize( + string memory _name, + string memory _symbol, + address _operator, + uint256 _fee, + address _feeCollector, + address _capManager + ) external; + function lastAvailableAssets() external view returns (int128); + function liquidityAsset() external view returns (address); + function minSharesToRedeem() external view returns (uint256); + function name() external view returns (string memory); + function nextWithdrawalIndex() external view returns (uint256); + function operator() external view returns (address); + function owner() external view returns (address); + function previewDeposit(uint256 assets) external view returns (uint256 shares); + function previewRedeem(uint256 shares) external view returns (uint256 assets); + function removeMarket(address _market) external; + function requestOriginWithdrawal(uint256 amount) external returns (uint256 requestId); + function requestRedeem(uint256 shares) external returns (uint256 requestId, uint256 assets); + function setARMBuffer(uint256 _armBuffer) external; + function setActiveMarket(address _market) external; + function setCapManager(address _capManager) external; + function setCrossPrice(uint256 newCrossPrice) external; + function setFee(uint256 _fee) external; + function setFeeCollector(address _feeCollector) external; + function setOperator(address newOperator) external; + function setOwner(address newOwner) external; + function setPrices(uint256 buyT1, uint256 sellT1) external; + function supportedMarkets(address market) external view returns (bool supported); + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] memory path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + function swapExactTokensForTokens( + address inToken, + address outToken, + uint256 amountIn, + uint256 amountOutMin, + address to + ) external; + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] memory path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + function swapTokensForExactTokens( + address inToken, + address outToken, + uint256 amountOut, + uint256 amountInMax, + address to + ) external; + function symbol() external view returns (string memory); + function token0() external view returns (address); + function token1() external view returns (address); + function totalAssets() external view returns (uint256); + function totalSupply() external view returns (uint256); + function traderate0() external view returns (uint256); + function traderate1() external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + function vault() external view returns (address); + function vaultWithdrawalAmount() external view returns (uint256); + function withdrawalRequests(uint256 requestId) + external + view + returns (address withdrawer, bool claimed, uint40 claimTimestamp, uint128 assets, uint128 queued); + function withdrawsClaimed() external view returns (uint128); + function withdrawsQueued() external view returns (uint128); +} \ No newline at end of file diff --git a/contracts/scripts/runlogs/2025_08.s.sol b/contracts/scripts/runlogs/2025_08.s.sol index b3ef7b41a9..708ea97611 100644 --- a/contracts/scripts/runlogs/2025_08.s.sol +++ b/contracts/scripts/runlogs/2025_08.s.sol @@ -267,20 +267,18 @@ contract Runlogs_2025_08_Base is SetupBase { contract Runlogs_2025_08_Sonic is SetupSonic { function run() public { - _2025_08_28(); + _2025_08_29(); } - function _2025_08_28() internal { + function _2025_08_29() internal { vm.startBroadcast(timelock); // Before + uint256 balance = os.balanceOf(address(Sonic.ARM)); // Main action - (bool success,) = Sonic.ARM.call( - abi.encodeWithSignature( - "setFeeCollector(address)", 0xBB077E716A5f1F1B63ed5244eBFf5214E50fec8c - ) - ); - require(success, "Failed to set fee collector"); + arm.requestOriginWithdrawal(balance); + arm.setCrossPrice(0.9999e36); + arm.setFeeCollector(0xBB077E716A5f1F1B63ed5244eBFf5214E50fec8c); // After vm.stopBroadcast(); diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index 68c936db86..0a5ead6c93 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -62,6 +62,9 @@ library Sonic { address public constant GOVERNOR = 0xAdDEA7933Db7d83855786EB43a238111C69B00b6; address public constant TIMELOCK = 0x31a91336414d3B955E494E7d485a6B06b55FC8fB; + // OS + address public constant OS = 0xb1e25689D55734FD3ffFc939c4C3Eb52DFf8A794; + // ARM address public constant ARM = 0x2F872623d1E1Af5835b08b0E49aAd2d81d649D30; } diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index 3c7ffb9cab..fdbc790e03 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -// Contracts - OUSD +// Contracts - OUSD import { OUSD } from "contracts/contracts/token/OUSD.sol"; // Contracts - OETH @@ -15,22 +15,28 @@ import { OETHBaseVaultCore } from "contracts/contracts/vault/OETHBaseVaultCore.s import { OETHBaseVaultAdmin } from "contracts/contracts/vault/OETHBaseVaultAdmin.sol"; import { OETHVaultValueChecker } from "contracts/contracts/strategies/VaultValueChecker.sol"; +// Contract - OS +import { OSonic } from "contracts/contracts/token/OSonic.sol"; + // Contracts - Strategies import { CurveAMOStrategy } from "contracts/contracts/strategies/CurveAMOStrategy.sol"; import { BaseCurveAMOStrategy } from "contracts/contracts/strategies/BaseCurveAMOStrategy.sol"; import { AerodromeAMOStrategy } from "contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol"; +// Contracts - ARM +import { ISonicARM } from "contracts/contracts/interfaces/arm/ISonicARM.sol"; + // Interfaces import { IWETH9 } from "contracts/contracts/interfaces/IWETH9.sol"; import { ICurveStableSwapNG } from "contracts/contracts/interfaces/ICurveStableSwapNG.sol"; // Helpers import { CrossChain, Mainnet, Base, Sonic } from "./Addresses.sol"; -import { Script } from "forge-std/Script.sol"; -import { Test } from "forge-std/Test.sol"; // Foundry +import { Script } from "forge-std/Script.sol"; +import { Test } from "forge-std/Test.sol"; abstract contract SetupMainnet is Test, Script { // Governance @@ -89,6 +95,12 @@ abstract contract SetupSonic is Test, Script { address public timelock = Sonic.TIMELOCK; address public strategist = CrossChain.STRATEGIST; + // OS + OSonic public os = OSonic(Sonic.OS); + + // ARM + ISonicARM public arm = ISonicARM(Sonic.ARM); + function setUp() public { // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block vm.createSelectFork(vm.envString("SONIC_PROVIDER_URL")); From 721403650e460497e464b2262ee8295e8dbd6d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 1 Sep 2025 10:15:18 +0200 Subject: [PATCH 17/34] fix: correct file extension in BroadcastConvertor script path --- contracts/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Makefile b/contracts/Makefile index 6d173be614..0bcc457123 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -52,6 +52,6 @@ script: @echo "$(BOLD_BLUE)Running script RunlogsDATE=$(DATE)_CHAIN=$(CHAIN) $(RESET) \n" forge script Runlogs_$(DATE)_$(CHAIN) @echo "$(BOLD_BLUE)\nConverting foundry broadcast messages to Safe format... $(RESET) \n" - forge script BroadcastConvertor contracts/broadcast/$(DATE).s.sol/$(CHAIN_ID)/dry-run/ + forge script BroadcastConvertor contracts/broadcast/$(DATE).sol/$(CHAIN_ID)/dry-run/ .PHONY: script \ No newline at end of file From c644b854c76cee37eb263fc1614a434d8471da20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 1 Sep 2025 10:15:27 +0200 Subject: [PATCH 18/34] feat: enhance ISonicARM interface and add new runlogs for Sonic integration; include new addresses and strategies --- .../contracts/interfaces/arm/ISonicARM.sol | 159 ++++++++++++++++-- contracts/scripts/runlogs/2025_09.sol | 64 +++++++ contracts/scripts/runlogs/utils/Addresses.sol | 9 + contracts/scripts/runlogs/utils/Setup.sol | 15 ++ 4 files changed, 229 insertions(+), 18 deletions(-) create mode 100644 contracts/scripts/runlogs/2025_09.sol diff --git a/contracts/contracts/interfaces/arm/ISonicARM.sol b/contracts/contracts/interfaces/arm/ISonicARM.sol index 9c4755d1a3..5f166cc779 100644 --- a/contracts/contracts/interfaces/arm/ISonicARM.sol +++ b/contracts/contracts/interfaces/arm/ISonicARM.sol @@ -2,8 +2,16 @@ pragma solidity ^0.8.4; interface ISonicARM { - error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); - error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + error ERC20InsufficientAllowance( + address spender, + uint256 allowance, + uint256 needed + ); + error ERC20InsufficientBalance( + address sender, + uint256 balance, + uint256 needed + ); error ERC20InvalidApprover(address approver); error ERC20InvalidReceiver(address receiver); error ERC20InvalidSender(address sender); @@ -19,7 +27,11 @@ interface ISonicARM { event ActiveMarketUpdated(address indexed market); event AdminChanged(address previousAdmin, address newAdmin); event Allocated(address indexed market, int256 assets); - event Approval(address indexed owner, address indexed spender, uint256 value); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); event CapManagerUpdated(address indexed capManager); event ClaimOriginWithdrawals(uint256[] requestIds, uint256 amountClaimed); event CrossPriceUpdated(uint256 crossPrice); @@ -31,41 +43,89 @@ interface ISonicARM { event MarketAdded(address indexed market); event MarketRemoved(address indexed market); event OperatorChanged(address newAdmin); - event RedeemClaimed(address indexed withdrawer, uint256 indexed requestId, uint256 assets); + event RedeemClaimed( + address indexed withdrawer, + uint256 indexed requestId, + uint256 assets + ); event RedeemRequested( - address indexed withdrawer, uint256 indexed requestId, uint256 assets, uint256 queued, uint256 claimTimestamp + address indexed withdrawer, + uint256 indexed requestId, + uint256 assets, + uint256 queued, + uint256 claimTimestamp ); event RequestOriginWithdrawal(uint256 amount, uint256 requestId); event TraderateChanged(uint256 traderate0, uint256 traderate1); event Transfer(address indexed from, address indexed to, uint256 value); function FEE_SCALE() external view returns (uint256); + function MAX_CROSS_PRICE_DEVIATION() external view returns (uint256); + function PRICE_SCALE() external view returns (uint256); + function activeMarket() external view returns (address); + function addMarkets(address[] memory _markets) external; + function allocate() external returns (int256 liquidityDelta); + function allocateThreshold() external view returns (int256); - function allowance(address owner, address spender) external view returns (uint256); + + function allowance(address owner, address spender) + external + view + returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function armBuffer() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function baseAsset() external view returns (address); + function capManager() external view returns (address); + function claimDelay() external view returns (uint256); - function claimOriginWithdrawals(uint256[] memory requestIds) external returns (uint256 amountClaimed); + + function claimOriginWithdrawals(uint256[] memory requestIds) + external + returns (uint256 amountClaimed); + function claimRedeem(uint256 requestId) external returns (uint256 assets); + function claimable() external view returns (uint256 claimableAmount); + function collectFees() external returns (uint256 fees); - function convertToAssets(uint256 shares) external view returns (uint256 assets); - function convertToShares(uint256 assets) external view returns (uint256 shares); + + function convertToAssets(uint256 shares) + external + view + returns (uint256 assets); + + function convertToShares(uint256 assets) + external + view + returns (uint256 shares); + function crossPrice() external view returns (uint256); + function decimals() external view returns (uint8); - function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + function deposit(uint256 assets, address receiver) + external + returns (uint256 shares); + function deposit(uint256 assets) external returns (uint256 shares); + function fee() external view returns (uint16); + function feeCollector() external view returns (address); + function feesAccrued() external view returns (uint256 fees); + function initialize( string memory _name, string memory _symbol, @@ -74,28 +134,64 @@ interface ISonicARM { address _feeCollector, address _capManager ) external; + function lastAvailableAssets() external view returns (int128); + function liquidityAsset() external view returns (address); + function minSharesToRedeem() external view returns (uint256); + function name() external view returns (string memory); + function nextWithdrawalIndex() external view returns (uint256); + function operator() external view returns (address); + function owner() external view returns (address); - function previewDeposit(uint256 assets) external view returns (uint256 shares); - function previewRedeem(uint256 shares) external view returns (uint256 assets); + + function previewDeposit(uint256 assets) + external + view + returns (uint256 shares); + + function previewRedeem(uint256 shares) + external + view + returns (uint256 assets); + function removeMarket(address _market) external; - function requestOriginWithdrawal(uint256 amount) external returns (uint256 requestId); - function requestRedeem(uint256 shares) external returns (uint256 requestId, uint256 assets); + + function requestOriginWithdrawal(uint256 amount) + external + returns (uint256 requestId); + + function requestRedeem(uint256 shares) + external + returns (uint256 requestId, uint256 assets); + function setARMBuffer(uint256 _armBuffer) external; + function setActiveMarket(address _market) external; + function setCapManager(address _capManager) external; + function setCrossPrice(uint256 newCrossPrice) external; + function setFee(uint256 _fee) external; + function setFeeCollector(address _feeCollector) external; + function setOperator(address newOperator) external; + function setOwner(address newOwner) external; + function setPrices(uint256 buyT1, uint256 sellT1) external; - function supportedMarkets(address market) external view returns (bool supported); + + function supportedMarkets(address market) + external + view + returns (bool supported); + function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, @@ -103,6 +199,7 @@ interface ISonicARM { address to, uint256 deadline ) external returns (uint256[] memory amounts); + function swapExactTokensForTokens( address inToken, address outToken, @@ -110,6 +207,7 @@ interface ISonicARM { uint256 amountOutMin, address to ) external; + function swapTokensForExactTokens( uint256 amountOut, uint256 amountInMax, @@ -117,6 +215,7 @@ interface ISonicARM { address to, uint256 deadline ) external returns (uint256[] memory amounts); + function swapTokensForExactTokens( address inToken, address outToken, @@ -124,21 +223,45 @@ interface ISonicARM { uint256 amountInMax, address to ) external; + function symbol() external view returns (string memory); + function token0() external view returns (address); + function token1() external view returns (address); + function totalAssets() external view returns (uint256); + function totalSupply() external view returns (uint256); + function traderate0() external view returns (uint256); + function traderate1() external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); - function transferFrom(address from, address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + function vault() external view returns (address); + function vaultWithdrawalAmount() external view returns (uint256); + function withdrawalRequests(uint256 requestId) external view - returns (address withdrawer, bool claimed, uint40 claimTimestamp, uint128 assets, uint128 queued); + returns ( + address withdrawer, + bool claimed, + uint40 claimTimestamp, + uint128 assets, + uint128 queued + ); + function withdrawsClaimed() external view returns (uint128); + function withdrawsQueued() external view returns (uint128); -} \ No newline at end of file +} diff --git a/contracts/scripts/runlogs/2025_09.sol b/contracts/scripts/runlogs/2025_09.sol new file mode 100644 index 0000000000..f1dd3be8f4 --- /dev/null +++ b/contracts/scripts/runlogs/2025_09.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +// Setup +import { SetupBase } from "./utils/Setup.sol"; +import { SetupSonic } from "./utils/Setup.sol"; +import { SetupMainnet } from "./utils/Setup.sol"; + +import { Sonic } from "./utils/Addresses.sol"; + +// Foundry +import { console } from "forge-std/console.sol"; + +contract Runlogs_2025_09_Mainnet is SetupMainnet { + function run() public { } +} + +contract Runlogs_2025_09_Base is SetupBase { + function run() public { } +} + +contract Runlogs_2025_09_Sonic is SetupSonic { + function run() public { + //_2025_09_01(); + _2025_09_01_BIS(); + } + + function _2025_09_01() internal { + vm.startBroadcast(localStrategist); + address[] memory tokens = new address[](1); + tokens[0] = address(ws); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 2_644_097 ether; + + // Before + osVaultCore.rebase(); + osVaultValueChecker.takeSnapshot(); + + // Main + osVaultAdmin.depositToStrategy(address(stakingStrategy), tokens, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSupplyAfter,) = + osVaultValueChecker.snapshots(localStrategist); + int256 vaultChange = int256(osVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(os.totalSupply()) - int256(totalSupplyAfter); + int256 profit = vaultChange - supplyChange; + osVaultValueChecker.checkDelta(profit, 10 ether, vaultChange, 10 ether); + + console.log("-----"); + console.log("Profit : %18e", profit); + console.log("Amount deposited : %18e", amounts[0], "ether"); + console.log("OS Supply change : %18e", supplyChange); + console.log("Vault value change: %18e", vaultChange); + + vm.stopBroadcast(); + } + + function _2025_09_01_BIS() internal { + vm.startBroadcast(timelock); + osVaultAdmin.setAssetDefaultStrategy(address(ws), address(stakingStrategy)); + vm.stopBroadcast(); + } +} diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index 0a5ead6c93..a801681d2d 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -61,10 +61,19 @@ library Sonic { // Governance address public constant GOVERNOR = 0xAdDEA7933Db7d83855786EB43a238111C69B00b6; address public constant TIMELOCK = 0x31a91336414d3B955E494E7d485a6B06b55FC8fB; + address public constant STRATEGIST = 0x63cdd3072F25664eeC6FAEFf6dAeB668Ea4de94a; // OS address public constant OS = 0xb1e25689D55734FD3ffFc939c4C3Eb52DFf8A794; + address public constant OS_VAULT = 0xa3c0eCA00D2B76b4d1F170b0AB3FdeA16C180186; + address public constant OS_VAULT_VALUE_CHECKER = 0x06f172e6852085eCa886B7f9fd8f7B21Db3D2c40; // ARM address public constant ARM = 0x2F872623d1E1Af5835b08b0E49aAd2d81d649D30; + + // Strategies + address public constant STAKING_STRATEGY = 0x596B0401479f6DfE1cAF8c12838311FeE742B95c; + + // Other token + address public constant WS = 0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38; } diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index fdbc790e03..3eb9caeb81 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -11,6 +11,8 @@ import { OETHBase } from "contracts/contracts/token/OETHBase.sol"; import { WOETHBase } from "contracts/contracts/token/WOETHBase.sol"; import { OETHVaultCore } from "contracts/contracts/vault/OETHVaultCore.sol"; import { OETHVaultAdmin } from "contracts/contracts/vault/OETHVaultAdmin.sol"; +import { OSonicVaultCore } from "contracts/contracts/vault/OSonicVaultCore.sol"; +import { OSonicVaultAdmin } from "contracts/contracts/vault/OSonicVaultAdmin.sol"; import { OETHBaseVaultCore } from "contracts/contracts/vault/OETHBaseVaultCore.sol"; import { OETHBaseVaultAdmin } from "contracts/contracts/vault/OETHBaseVaultAdmin.sol"; import { OETHVaultValueChecker } from "contracts/contracts/strategies/VaultValueChecker.sol"; @@ -21,6 +23,7 @@ import { OSonic } from "contracts/contracts/token/OSonic.sol"; // Contracts - Strategies import { CurveAMOStrategy } from "contracts/contracts/strategies/CurveAMOStrategy.sol"; import { BaseCurveAMOStrategy } from "contracts/contracts/strategies/BaseCurveAMOStrategy.sol"; +import { SonicStakingStrategy } from "contracts/contracts/strategies/sonic/SonicStakingStrategy.sol"; import { AerodromeAMOStrategy } from "contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol"; @@ -94,13 +97,25 @@ abstract contract SetupSonic is Test, Script { address public governor = Sonic.GOVERNOR; address public timelock = Sonic.TIMELOCK; address public strategist = CrossChain.STRATEGIST; + address public localStrategist = Sonic.STRATEGIST; // OS OSonic public os = OSonic(Sonic.OS); + OSonicVaultCore public osVaultCore = OSonicVaultCore(Sonic.OS_VAULT); + OSonicVaultAdmin public osVaultAdmin = OSonicVaultAdmin(Sonic.OS_VAULT); + OETHVaultValueChecker public osVaultValueChecker = + OETHVaultValueChecker(Sonic.OS_VAULT_VALUE_CHECKER); + + // Interfaces + IWETH9 public ws = IWETH9(Sonic.WS); // ARM ISonicARM public arm = ISonicARM(Sonic.ARM); + // Staking strategy + SonicStakingStrategy public stakingStrategy = + SonicStakingStrategy(payable(Sonic.STAKING_STRATEGY)); + function setUp() public { // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block vm.createSelectFork(vm.envString("SONIC_PROVIDER_URL")); From ea1384cce2f7d102e9953f97c3ea4dd07b9e8894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:29:43 +0200 Subject: [PATCH 19/34] Revise runlogs conversion instructions in README --- contracts/scripts/runlogs/README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/contracts/scripts/runlogs/README.md b/contracts/scripts/runlogs/README.md index 76ce2566e1..6c30a20ad5 100644 --- a/contracts/scripts/runlogs/README.md +++ b/contracts/scripts/runlogs/README.md @@ -22,27 +22,22 @@ This generates 2 files (that are `broadcast-ready` for execution) under `contrac ## 3. Convert runlogs into Safe-compatible JSON Since these transactions are meant to be executed from the Safe, it is not possible to use `cast`. -To convert a `broadcast-ready` into a Safe-compatible JSON file, use the script: `scripts/runlogs/broadcast_convertor.py`. +To convert a `broadcast-ready` into a Safe-compatible JSON file, use the forge script: `scripts/runlogs/utils/BroadcastConvertor.sol`. In the `contracts` folder run: ```bash -python3 scripts/runlogs/broadcast_convertor.py -i broadcast/2025_08.s.sol/1/dry-run/run-latest.json +forge script BroadcastConvertor contracts/broadcast/2025_09.sol/146/dry-run/ ``` -> Note adjust the input accordingly. +> Note adjust the input accordingly: +> first the path to the run file, but stop at the dry-run folder. This creates, by default, a file named `run-latest-safe.json` in the same location as the input file, ready to be imported into the Safe UI. +### Timelock targeted ? +If on the script, the address used inside `startBroadcast()` is a `Timelock`, the Safe-compatible JSON will be adjusted to target the `scheduleBatch` function on the `Timelock` contract. -## 4. Options for `broadcast_convertor.py` -``` - -h, --help show this help message and exit - --input, -i INPUT Path to input JSON file (MANDATORY) - --output, -o OUTPUT Path to output JSON file (default: adds -safe to input filename) - --suffix SUFFIX Suffix to add to filename (default: safe) - --display, -d DISPLAY Display the output JSON in the console -``` -## 5. How generates Safe JSON in just one command? +## 4. How generates Safe JSON in just one command? In the `contracts` folder: ```makefile make script From d2e17e35de53c954ae5a3ede159f5fc1b8aa492b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Mon, 1 Sep 2025 12:13:47 +0200 Subject: [PATCH 20/34] feat: enhance BroadcastConvertor to support Timelock scheduling and execution; update prepareForSafe and transactionForTimelock functions --- .../runlogs/utils/BroadcastConvertor.sol | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/contracts/scripts/runlogs/utils/BroadcastConvertor.sol b/contracts/scripts/runlogs/utils/BroadcastConvertor.sol index 023654083c..e9760c961f 100644 --- a/contracts/scripts/runlogs/utils/BroadcastConvertor.sol +++ b/contracts/scripts/runlogs/utils/BroadcastConvertor.sol @@ -63,19 +63,34 @@ contract BroadcastConvertor is Script, Test { // Convert Json into Json-Struct Json memory json = abi.decode(vm.parseJson(inputJson), (Json)); - // Prepare for Safe - string memory safeJson = prepareForSafe(json); - - // Print Safe JSON - console.log("Use this JSON is Safe:\n%s", safeJson); + // Is timelock targeted? + uint256 delay = timelockDelay(json); - // Write JSON - vm.writeJson(safeJson, string.concat(path, "run-latest-safe.json")); + if (delay > 0) { + // Prepare schedule for Timelock + string memory scheduleJson = prepareForSafe(json, Timelock.scheduleBatch.selector); + console.log("\nUse this JSON to schedule on Timelock:\n%s", scheduleJson); + + // Prepare execute for Timelock + string memory executeJson = prepareForSafe(json, Timelock.executeBatch.selector); + console.log("\nUse this JSON to execute on Timelock:\n%s", executeJson); + + // Write both JSONs + vm.writeJson(scheduleJson, string.concat(path, "run-latest-schedule.json")); + vm.writeJson(executeJson, string.concat(path, "run-latest-execute.json")); + } else { + // Prepare for Safe + string memory safeJson = prepareForSafe(json, bytes4(0)); + console.log("Use this JSON is Safe:\n%s", safeJson); + + // Write Safe JSON + vm.writeJson(safeJson, string.concat(path, "run-latest-safe.json")); + } } /// @notice Prepares the JSON for Safe format /// @param json The original JSON in Json-Struct format - function prepareForSafe(Json memory json) public pure returns (string memory) { + function prepareForSafe(Json memory json, bytes4 selector) public pure returns (string memory) { // Header string memory header = string.concat( '{ "version": "1.0", "chainId": "', @@ -95,7 +110,7 @@ contract BroadcastConvertor is Script, Test { // Transactions string memory transactions = string.concat( '"transactions": [ ', - delay == 0 ? rawTransactions(json) : transactionForTimelock(json, delay), + delay == 0 ? rawTransactions(json) : transactionForTimelock(json, delay, selector), " ]," ); @@ -145,7 +160,7 @@ contract BroadcastConvertor is Script, Test { /// @dev This function will batch all the tx in a single one, and call the timelock pranked on the runlog /// @param json The original JSON in Json-Struct format /// @param delay The delay for the timelock - function transactionForTimelock(Json memory json, uint256 delay) + function transactionForTimelock(Json memory json, uint256 delay, bytes4 selector) public pure returns (string memory transaction) @@ -172,11 +187,9 @@ contract BroadcastConvertor is Script, Test { transaction = string.concat( transaction, ' , "data": "', - vm.toString( - abi.encodeWithSelector( - Timelock.scheduleBatch.selector, targets, values, payloads, 0, 0, delay - ) - ), + selector == (Timelock.scheduleBatch.selector) + ? vm.toString(abi.encodeWithSelector(selector, targets, values, payloads, 0, 0, delay)) + : vm.toString(abi.encodeWithSelector(selector, targets, values, payloads, 0, 0)), '",' ); @@ -209,4 +222,12 @@ interface Timelock { bytes32 salt, uint256 delay ) external; + + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata dataElements, + bytes32 predecessor, + bytes32 salt + ) external payable; } From 8fc2dc53d717047b7e6ac3e3ffed167c6548cfff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:19:42 +0200 Subject: [PATCH 21/34] Update README.md --- contracts/scripts/runlogs/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/scripts/runlogs/README.md b/contracts/scripts/runlogs/README.md index 6c30a20ad5..3f96171b12 100644 --- a/contracts/scripts/runlogs/README.md +++ b/contracts/scripts/runlogs/README.md @@ -34,7 +34,9 @@ forge script BroadcastConvertor contracts/broadcast/2025_09.sol/146/dry-run/ This creates, by default, a file named `run-latest-safe.json` in the same location as the input file, ready to be imported into the Safe UI. ### Timelock targeted ? -If on the script, the address used inside `startBroadcast()` is a `Timelock`, the Safe-compatible JSON will be adjusted to target the `scheduleBatch` function on the `Timelock` contract. +If on the script, the address used inside `startBroadcast()` is a `Timelock`: +- the Safe-compatible JSON will be adjusted to target the `scheduleBatch` and `executeBatch` functions on the `Timelock` contract. +- two files will be generated: `run-latest-schedule` and `run-latest-execute`. ## 4. How generates Safe JSON in just one command? From 37f4938efe9ef64815d9f1d3058d73dc166b5058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 2 Sep 2025 09:41:21 +0200 Subject: [PATCH 22/34] feat: update Makefile to correct script path and add new runlogs for 2025_09 --- contracts/Makefile | 2 +- contracts/scripts/runlogs/{2025_09.sol => 2025_09.s.sol} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/scripts/runlogs/{2025_09.sol => 2025_09.s.sol} (100%) diff --git a/contracts/Makefile b/contracts/Makefile index 0bcc457123..6d173be614 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -52,6 +52,6 @@ script: @echo "$(BOLD_BLUE)Running script RunlogsDATE=$(DATE)_CHAIN=$(CHAIN) $(RESET) \n" forge script Runlogs_$(DATE)_$(CHAIN) @echo "$(BOLD_BLUE)\nConverting foundry broadcast messages to Safe format... $(RESET) \n" - forge script BroadcastConvertor contracts/broadcast/$(DATE).sol/$(CHAIN_ID)/dry-run/ + forge script BroadcastConvertor contracts/broadcast/$(DATE).s.sol/$(CHAIN_ID)/dry-run/ .PHONY: script \ No newline at end of file diff --git a/contracts/scripts/runlogs/2025_09.sol b/contracts/scripts/runlogs/2025_09.s.sol similarity index 100% rename from contracts/scripts/runlogs/2025_09.sol rename to contracts/scripts/runlogs/2025_09.s.sol From 2a5078e314eaae5e1bb55db79c32843af8287c3a Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:43:10 +0400 Subject: [PATCH 23/34] Add runlog --- contracts/scripts/runlogs/2025_09.s.sol | 33 ++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index f1dd3be8f4..f03e6889e1 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -12,7 +12,38 @@ import { Sonic } from "./utils/Addresses.sol"; import { console } from "forge-std/console.sol"; contract Runlogs_2025_09_Mainnet is SetupMainnet { - function run() public { } + function run() public { + _2025_09_03(); + } + + function _2025_09_03() internal { + vm.startBroadcast(strategist); + + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 7 ether; + oethVaultAdmin.withdrawFromStrategy(address(oethWethCurveAMO), assets, amounts); + + (uint256 vaultValueAfter, uint256 totalSupplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSupplyAfter); + int256 profit = vaultChange - supplyChange; + oethVaultValueChecker.checkDelta(profit, 0.1 ether, vaultChange, 1 ether); + + console.log("-----"); + console.log("Profit : %18e", profit); + console.log("Amount withdrawn : %18e", amounts[0], "ether"); + console.log("Supply change : %18e", supplyChange); + console.log("Vault value change : %18e", vaultChange); + + + vm.stopBroadcast(); + } } contract Runlogs_2025_09_Base is SetupBase { From 7d9ef972e40e36d5840ef6499bf8807f88d74677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 4 Sep 2025 22:45:07 +0200 Subject: [PATCH 24/34] feat: implement deposit functionality for Curve AMO in runlogs for September 04, 2025 --- contracts/scripts/runlogs/2025_09.s.sol | 64 ++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index f03e6889e1..77e9a3d9df 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -47,7 +47,69 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { } contract Runlogs_2025_09_Base is SetupBase { - function run() public { } + function run() public { + _2025_09_04(); + } + + // ------------------------------------------------------------------ + // September 04, 2025 - Deposit 160 WETH on Curve AMO + // ------------------------------------------------------------------ + function _2025_09_04() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToDeposit = 160 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToDeposit; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); + + console.log("-----"); + console.log("Profit : %18e ", profit); + console.log("OETH Supply change: %18e ", supplyChange); + console.log("Vault value change: %18e ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(0, 1, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + } } contract Runlogs_2025_09_Sonic is SetupSonic { From 8cf7ddafd09f58b3cdbe587a7b927450b33280d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Tue, 9 Sep 2025 17:45:07 +0200 Subject: [PATCH 25/34] feat: update runlogs for September 09, 2025; implement deposit of 125 WETH on Curve AMO and adjust pool balance calculations --- contracts/scripts/runlogs/2025_09.s.sol | 68 +++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index 77e9a3d9df..b13191f9c4 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -41,14 +41,14 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { console.log("Supply change : %18e", supplyChange); console.log("Vault value change : %18e", vaultChange); - vm.stopBroadcast(); } } contract Runlogs_2025_09_Base is SetupBase { function run() public { - _2025_09_04(); + //_2025_09_04(); + _2025_09_09(); } // ------------------------------------------------------------------ @@ -64,7 +64,7 @@ contract Runlogs_2025_09_Base is SetupBase { uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; - uint256 wethOutBefore = oethWethCurvePool.get_dy(0, 1, 10 ether); + uint256 wethOutBefore = oethWethCurvePool.get_dy(1, 0, 10 ether); console.log("-----"); console.log("Curve OETH/WETH Pool before"); @@ -98,7 +98,67 @@ contract Runlogs_2025_09_Base is SetupBase { uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; - uint256 wethOutAfter = oethWethCurvePool.get_dy(0, 1, 10 ether); + uint256 wethOutAfter = oethWethCurvePool.get_dy(1, 0, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + } + + // ------------------------------------------------------------------ + // September 09, 2025 - Deposit 125 WETH on Curve AMO + // ------------------------------------------------------------------ + function _2025_09_09() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(1, 0, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToDeposit = 125 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToDeposit; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); + + console.log("-----"); + console.log("Profit : %18e ", profit); + console.log("OETH Supply change: %18e ", supplyChange); + console.log("Vault value change: %18e ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(1, 0, 10 ether); console.log("-----"); console.log("Curve OETH/WETH Pool after"); From 3bfcd3f8f21b3551aa2c131799f11509cf5515c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 11 Sep 2025 17:04:01 +0200 Subject: [PATCH 26/34] feat: add Pool Booster functionality and update runlogs for September 11, 2025 --- contracts/scripts/runlogs/2025_09.s.sol | 30 +++++++++++++++++-- contracts/scripts/runlogs/utils/Addresses.sol | 7 +++++ contracts/scripts/runlogs/utils/Setup.sol | 12 ++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index b13191f9c4..bbc08eb944 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -6,7 +6,7 @@ import { SetupBase } from "./utils/Setup.sol"; import { SetupSonic } from "./utils/Setup.sol"; import { SetupMainnet } from "./utils/Setup.sol"; -import { Sonic } from "./utils/Addresses.sol"; +import { CrossChain } from "./utils/Addresses.sol"; // Foundry import { console } from "forge-std/console.sol"; @@ -48,7 +48,8 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { contract Runlogs_2025_09_Base is SetupBase { function run() public { //_2025_09_04(); - _2025_09_09(); + //_2025_09_09(); + _2025_11_09(); } // ------------------------------------------------------------------ @@ -170,6 +171,31 @@ contract Runlogs_2025_09_Base is SetupBase { ); vm.stopBroadcast(); } + + // ------------------------------------------------------------------ + // September 11, 2025 - Deploy Merkl Pool Booster + Yield Forward + // ------------------------------------------------------------------ + function _2025_11_09() internal { + bytes memory campaignData = + "0x67a66cbacb2fe48ec4326932d4528215ad11656a86135f2795f5b90e501eb53800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + vm.startBroadcast(strategist); + // Create the pool booster + poolBoosterFactoryMerkl.createPoolBoosterMerkl({ + _campaignType: 1, + _ammPoolAddress: CrossChain.MORPHO_BLUE, + _campaignDuration: 1 days, + campaignData: campaignData, + _salt: uint256(keccak256(abi.encodePacked(block.timestamp))) + }); + + uint256 length = poolBoosterFactoryMerkl.poolBoosterLength(); + (address pb,,) = poolBoosterFactoryMerkl.poolBoosters(length - 1); + + // Run yield forward + oeth.delegateYield(CrossChain.MORPHO_BLUE, address(pb)); + vm.stopBroadcast(); + } } contract Runlogs_2025_09_Sonic is SetupSonic { diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index a801681d2d..31073e3aa0 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.0; library CrossChain { // Governance address public constant STRATEGIST = 0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971; + + // Protocols + address public constant MORPHO_BLUE = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; } library Mainnet { @@ -48,6 +51,10 @@ library Base { address public constant OETHB_WETH_CURVE_AMO = 0x9cfcAF81600155e01c63e4D2993A8A81A8205829; address public constant OETHB_WETH_AERODROME_POOL = 0xF611cC500eEE7E4e4763A05FE623E2363c86d2Af; + // Pool Booster + address public constant POOL_BOOSTER_FACTORY_MERKL = 0x1ADB902Ece465cA681C66187627a622a631a0a63; + address public constant POOL_BOOSTER_CENTRAL_REGISTRY = 0x157f0B239D7F83D153E6c95F8AD9d341694376E3; + // Other token address public constant WETH = 0x4200000000000000000000000000000000000006; diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index 3eb9caeb81..17e32d7838 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -30,6 +30,12 @@ import { AerodromeAMOStrategy } from // Contracts - ARM import { ISonicARM } from "contracts/contracts/interfaces/arm/ISonicARM.sol"; +// Contracts - Pool Booster +import { PoolBoosterFactoryMerkl } from + "contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol"; +import { PoolBoostCentralRegistry } from + "contracts/contracts/poolBooster/PoolBoostCentralRegistry.sol"; + // Interfaces import { IWETH9 } from "contracts/contracts/interfaces/IWETH9.sol"; import { ICurveStableSwapNG } from "contracts/contracts/interfaces/ICurveStableSwapNG.sol"; @@ -82,6 +88,12 @@ abstract contract SetupBase is Test, Script { OETHVaultValueChecker public oethVaultValueChecker = OETHVaultValueChecker(Base.OETHB_VAULT_VALUE_CHECKER); + // Pool Booster + PoolBoosterFactoryMerkl public poolBoosterFactoryMerkl = + PoolBoosterFactoryMerkl(Base.POOL_BOOSTER_FACTORY_MERKL); + PoolBoostCentralRegistry public poolBoosterCentralRegistry = + PoolBoostCentralRegistry(Base.POOL_BOOSTER_CENTRAL_REGISTRY); + // Interfaces IWETH9 public weth = IWETH9(Base.WETH); ICurveStableSwapNG public oethWethCurvePool = ICurveStableSwapNG(Base.OETHB_WETH_CURVE_POOL); From 13b995f219ff3e72456883e89708e075960e7e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 11 Sep 2025 17:06:03 +0200 Subject: [PATCH 27/34] feat: update salt generation for Merkl Pool Booster to use a fixed string --- contracts/scripts/runlogs/2025_09.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index bbc08eb944..2bcfe162c9 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -186,7 +186,7 @@ contract Runlogs_2025_09_Base is SetupBase { _ammPoolAddress: CrossChain.MORPHO_BLUE, _campaignDuration: 1 days, campaignData: campaignData, - _salt: uint256(keccak256(abi.encodePacked(block.timestamp))) + _salt: uint256(keccak256(abi.encodePacked("Merkl Pool Booster V1"))) }); uint256 length = poolBoosterFactoryMerkl.poolBoosterLength(); From 02ced4356e0dcc47a8094c2305a7e8504536c7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 11 Sep 2025 17:06:10 +0200 Subject: [PATCH 28/34] feat: add verbose flag to forge script command in Makefile --- contracts/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Makefile b/contracts/Makefile index 6d173be614..598629fcb1 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -50,7 +50,7 @@ endef script: $(eval CHAIN_ID := $(call get_chain_id)) @echo "$(BOLD_BLUE)Running script RunlogsDATE=$(DATE)_CHAIN=$(CHAIN) $(RESET) \n" - forge script Runlogs_$(DATE)_$(CHAIN) + forge script Runlogs_$(DATE)_$(CHAIN) -vvvvv @echo "$(BOLD_BLUE)\nConverting foundry broadcast messages to Safe format... $(RESET) \n" forge script BroadcastConvertor contracts/broadcast/$(DATE).s.sol/$(CHAIN_ID)/dry-run/ From a76723785f0f5a6a7c9c3aab0542e2be6d272d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 11 Sep 2025 17:11:15 +0200 Subject: [PATCH 29/34] refactor: comment out unused pool booster logic in Runlogs_2025_09_Base --- contracts/scripts/runlogs/2025_09.s.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index 2bcfe162c9..49e3bac8fa 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -189,11 +189,11 @@ contract Runlogs_2025_09_Base is SetupBase { _salt: uint256(keccak256(abi.encodePacked("Merkl Pool Booster V1"))) }); - uint256 length = poolBoosterFactoryMerkl.poolBoosterLength(); - (address pb,,) = poolBoosterFactoryMerkl.poolBoosters(length - 1); + //uint256 length = poolBoosterFactoryMerkl.poolBoosterLength(); + //(address pb,,) = poolBoosterFactoryMerkl.poolBoosters(length - 1); // Run yield forward - oeth.delegateYield(CrossChain.MORPHO_BLUE, address(pb)); + //oeth.delegateYield(CrossChain.MORPHO_BLUE, address(pb)); vm.stopBroadcast(); } } From ac72912057902ef3301683bc7e58cdbcfee0c2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 18 Sep 2025 14:09:02 +0200 Subject: [PATCH 30/34] feat: update September 11, 2025 runlog to deploy Merkl Pool Booster with adjusted parameters --- contracts/scripts/runlogs/2025_09.s.sol | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index 49e3bac8fa..b4018f0e65 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -49,7 +49,7 @@ contract Runlogs_2025_09_Base is SetupBase { function run() public { //_2025_09_04(); //_2025_09_09(); - _2025_11_09(); + _2025_16_09(); } // ------------------------------------------------------------------ @@ -175,25 +175,26 @@ contract Runlogs_2025_09_Base is SetupBase { // ------------------------------------------------------------------ // September 11, 2025 - Deploy Merkl Pool Booster + Yield Forward // ------------------------------------------------------------------ - function _2025_11_09() internal { + function _2025_16_09() internal { bytes memory campaignData = - "0x67a66cbacb2fe48ec4326932d4528215ad11656a86135f2795f5b90e501eb53800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + hex"67a66cbacb2fe48ec4326932d4528215ad11656a86135f2795f5b90e501eb53800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; vm.startBroadcast(strategist); // Create the pool booster poolBoosterFactoryMerkl.createPoolBoosterMerkl({ - _campaignType: 1, + _campaignType: 45, _ammPoolAddress: CrossChain.MORPHO_BLUE, - _campaignDuration: 1 days, + _campaignDuration: 7 days, campaignData: campaignData, _salt: uint256(keccak256(abi.encodePacked("Merkl Pool Booster V1"))) }); - //uint256 length = poolBoosterFactoryMerkl.poolBoosterLength(); - //(address pb,,) = poolBoosterFactoryMerkl.poolBoosters(length - 1); + uint256 length = poolBoosterFactoryMerkl.poolBoosterLength(); + (address pb,,) = poolBoosterFactoryMerkl.poolBoosters(length - 1); // Run yield forward - //oeth.delegateYield(CrossChain.MORPHO_BLUE, address(pb)); + oeth.undelegateYield(CrossChain.MORPHO_BLUE); + oeth.delegateYield(CrossChain.MORPHO_BLUE, address(pb)); vm.stopBroadcast(); } } From 9220e1717fa35c3527e0e94c39b4fc42c04fdf4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 18 Sep 2025 15:01:51 +0200 Subject: [PATCH 31/34] feat: implement September 18, 2025 runlog for OETH/WETH pool deposit and profit calculation --- contracts/scripts/runlogs/2025_09.s.sol | 94 ++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index b4018f0e65..d51e630147 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -13,7 +13,8 @@ import { console } from "forge-std/console.sol"; contract Runlogs_2025_09_Mainnet is SetupMainnet { function run() public { - _2025_09_03(); + //_2025_09_03(); + _2025_09_18(); } function _2025_09_03() internal { @@ -43,13 +44,42 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { vm.stopBroadcast(); } + + function _2025_09_18() internal { + vm.startBroadcast(strategist); + + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 42 ether; + oethVaultAdmin.withdrawFromStrategy(address(oethWethCurveAMO), assets, amounts); + + (uint256 vaultValueAfter, uint256 totalSupplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSupplyAfter); + int256 profit = vaultChange - supplyChange; + oethVaultValueChecker.checkDelta(profit, 0.1 ether, vaultChange, 1 ether); + + console.log("-----"); + console.log("Profit : %18e", profit); + console.log("Amount withdrawn : %18e", amounts[0], "ether"); + console.log("Supply change : %18e", supplyChange); + console.log("Vault value change : %18e", vaultChange); + + vm.stopBroadcast(); + } } contract Runlogs_2025_09_Base is SetupBase { function run() public { //_2025_09_04(); //_2025_09_09(); - _2025_16_09(); + //_2025_16_09(); + _2025_09_18(); } // ------------------------------------------------------------------ @@ -197,6 +227,66 @@ contract Runlogs_2025_09_Base is SetupBase { oeth.delegateYield(CrossChain.MORPHO_BLUE, address(pb)); vm.stopBroadcast(); } + + // ------------------------------------------------------------------ + // September 18, 2025 - Deposit 50 WETH on Curve AMO + // ------------------------------------------------------------------ + function _2025_09_18() internal { + vm.startBroadcast(strategist); + // Before + oethVaultCore.rebase(); + oethVaultValueChecker.takeSnapshot(); + + // AMO pool before + uint256 wethPoolBalanceBefore = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceBefore = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolBefore = wethPoolBalanceBefore + oethPoolBalanceBefore; + uint256 wethOutBefore = oethWethCurvePool.get_dy(1, 0, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool before"); + console.log("WETH Pool %18e", wethPoolBalanceBefore); + console.log("OETH Pool %18e", oethPoolBalanceBefore); + console.log("Total Pool %18e", totalPoolBefore); + + // Main action + uint256 amountToDeposit = 50 ether; + address[] memory assets = new address[](1); + assets[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountToDeposit; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); + + // After + (uint256 vaultValueAfter, uint256 totalSuplyAfter,) = + oethVaultValueChecker.snapshots(strategist); + int256 vaultChange = int256(oethVaultCore.totalValue()) - int256(vaultValueAfter); + int256 supplyChange = int256(oeth.totalSupply()) - int256(totalSuplyAfter); + int256 profit = vaultChange - supplyChange; + + oethVaultValueChecker.checkDelta(profit, 1 ether, vaultChange, 10 ether); + + console.log("-----"); + console.log("Profit : %18e ", profit); + console.log("OETH Supply change: %18e ", supplyChange); + console.log("Vault value change: %18e ", vaultChange); + + // AMO pool after + uint256 wethPoolBalanceAfter = weth.balanceOf(address(oethWethCurvePool)); + uint256 oethPoolBalanceAfter = oeth.balanceOf(address(oethWethCurvePool)); + uint256 totalPoolAfter = wethPoolBalanceAfter + oethPoolBalanceAfter; + uint256 wethOutAfter = oethWethCurvePool.get_dy(1, 0, 10 ether); + + console.log("-----"); + console.log("Curve OETH/WETH Pool after"); + console.log("WETH Pool %18e", wethPoolBalanceAfter); + console.log("OETH Pool %18e", oethPoolBalanceAfter); + console.log("Total Pool %18e", totalPoolAfter); + console.log( + "Sell 10 OETH Curve prices before and after: %18e || %18e", wethOutBefore, wethOutAfter + ); + vm.stopBroadcast(); + } } contract Runlogs_2025_09_Sonic is SetupSonic { From 444264b443153791fa531cb1393d90483a827637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 18 Sep 2025 23:29:40 +0200 Subject: [PATCH 32/34] feat: update September 18, 2025 runlog to deposit 86 WETH on Curve AMO --- contracts/scripts/runlogs/2025_09.s.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/scripts/runlogs/2025_09.s.sol b/contracts/scripts/runlogs/2025_09.s.sol index d51e630147..2a798e03bc 100644 --- a/contracts/scripts/runlogs/2025_09.s.sol +++ b/contracts/scripts/runlogs/2025_09.s.sol @@ -17,6 +17,9 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { _2025_09_18(); } + // ------------------------------------------------------------------ + // September 03, 2025 - Withdraw 7 WETH on Curve AMO + // ------------------------------------------------------------------ function _2025_09_03() internal { vm.startBroadcast(strategist); @@ -45,6 +48,9 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { vm.stopBroadcast(); } + // ------------------------------------------------------------------ + // September 18, 2025 - Deposit 86 WETH on Curve AMO + // ------------------------------------------------------------------ function _2025_09_18() internal { vm.startBroadcast(strategist); @@ -54,8 +60,8 @@ contract Runlogs_2025_09_Mainnet is SetupMainnet { address[] memory assets = new address[](1); assets[0] = address(weth); uint256[] memory amounts = new uint256[](1); - amounts[0] = 42 ether; - oethVaultAdmin.withdrawFromStrategy(address(oethWethCurveAMO), assets, amounts); + amounts[0] = 86 ether; + oethVaultAdmin.depositToStrategy(address(oethWethCurveAMO), assets, amounts); (uint256 vaultValueAfter, uint256 totalSupplyAfter,) = oethVaultValueChecker.snapshots(strategist); From 6e8c657e55b8ab496266a271a110324d710c7d84 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Mon, 6 Oct 2025 11:03:56 +0200 Subject: [PATCH 33/34] Yield forwarding for a Merkl Pool booster (#2678) * add runlog to configure yield forwarding for a PB that is yet to be created * correct the salt --- contracts/scripts/runlogs/2025_10.s.sol | 67 +++++++++++++++++++ contracts/scripts/runlogs/README.md | 2 +- contracts/scripts/runlogs/utils/Addresses.sol | 4 ++ contracts/scripts/runlogs/utils/Setup.sol | 6 ++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 contracts/scripts/runlogs/2025_10.s.sol diff --git a/contracts/scripts/runlogs/2025_10.s.sol b/contracts/scripts/runlogs/2025_10.s.sol new file mode 100644 index 0000000000..79a86be0a0 --- /dev/null +++ b/contracts/scripts/runlogs/2025_10.s.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +// Setup +import { SetupBase } from "./utils/Setup.sol"; +import { SetupSonic } from "./utils/Setup.sol"; +import { SetupMainnet } from "./utils/Setup.sol"; + +import { CrossChain } from "./utils/Addresses.sol"; + +// Foundry +import { console } from "forge-std/console.sol"; + +contract Runlogs_2025_10_Mainnet is SetupMainnet { + function run() public { + _2025_10_01(); + //_2025_10_02(); + } + + // ------------------------------------------------------------------ + // Oct 3, 2025 - Yield Forward to Computed Merkl Pool Booster + // ------------------------------------------------------------------ + function _2025_10_01() internal { + bytes memory campaignData = + hex"b8fef900b383db2dbbf4458c7f46acf5b140f26d603a6d1829963f241b82510e00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + vm.startBroadcast(strategist); + + console.log("-----"); + console.log("strategist address", address(strategist)); + console.log("poolBoosterFactoryMerkl address", address(poolBoosterFactoryMerkl)); + + address poolBoosterAddress = poolBoosterFactoryMerkl.computePoolBoosterAddress({ + _campaignType: 45, + _ammPoolAddress: CrossChain.MORPHO_BLUE, + _campaignDuration: 7 days, + campaignData: campaignData, + _salt: uint256(keccak256(abi.encodePacked("Merkl Morpho PB OETH/USDC v1"))) + }); + + console.log("computed address", poolBoosterAddress); + + // Run yield forward + oeth.delegateYield(CrossChain.MORPHO_BLUE, poolBoosterAddress); + vm.stopBroadcast(); + } + +// ------------------------------------------------------------------ + // Oct 3+ TODO, 2025 - Create Merkl Pool Booster once Central Registry governance passes + // ------------------------------------------------------------------ + function _2025_10_02() internal { + bytes memory campaignData = + hex"b8fef900b383db2dbbf4458c7f46acf5b140f26d603a6d1829963f241b82510e00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + vm.startBroadcast(strategist); + // Create the pool booster + poolBoosterFactoryMerkl.createPoolBoosterMerkl({ + _campaignType: 45, // Incentivise Borrow rate of OETH/USDC + _ammPoolAddress: CrossChain.MORPHO_BLUE, + _campaignDuration: 7 days, + campaignData: campaignData, + _salt: uint256(keccak256(abi.encodePacked("Merkl Morpho PB OETH/USDC v1"))) + }); + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/contracts/scripts/runlogs/README.md b/contracts/scripts/runlogs/README.md index 3f96171b12..197260223c 100644 --- a/contracts/scripts/runlogs/README.md +++ b/contracts/scripts/runlogs/README.md @@ -26,7 +26,7 @@ To convert a `broadcast-ready` into a Safe-compatible JSON file, use the forge s In the `contracts` folder run: ```bash -forge script BroadcastConvertor contracts/broadcast/2025_09.sol/146/dry-run/ +forge script BroadcastConvertor --sig "run(string)" contracts/broadcast/2025_09.sol/146/dry-run/ ``` > Note adjust the input accordingly: > first the path to the run file, but stop at the dry-run folder. diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index 31073e3aa0..281dfdbe68 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -27,6 +27,10 @@ library Mainnet { // OETH Strategies address public constant OETH_WETH_CURVE_AMO = 0xba0e352AB5c13861C26e4E773e7a833C3A223FE6; + // Pool Booster + address public constant POOL_BOOSTER_FACTORY_MERKL = 0x0FC66355B681503eFeE7741BD848080d809FD6db; + address public constant POOL_BOOSTER_CENTRAL_REGISTRY = 0xAA8af8Db4B6a827B51786334d26349eb03569731; + // Other token address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index 17e32d7838..cd09a4d079 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -63,6 +63,12 @@ abstract contract SetupMainnet is Test, Script { OETHVaultValueChecker public oethVaultValueChecker = OETHVaultValueChecker(Mainnet.OETH_VAULT_VALUE_CHECKER); + // Pool Booster + PoolBoosterFactoryMerkl public poolBoosterFactoryMerkl = + PoolBoosterFactoryMerkl(Mainnet.POOL_BOOSTER_FACTORY_MERKL); + PoolBoostCentralRegistry public poolBoosterCentralRegistry = + PoolBoostCentralRegistry(Mainnet.POOL_BOOSTER_CENTRAL_REGISTRY); + // Interfaces IWETH9 public weth = IWETH9(Mainnet.WETH); ICurveStableSwapNG public oethWethCurvePool = ICurveStableSwapNG(Mainnet.OETH_WETH_CURVE_POOL); From c07477a2094c8b66c7f34f15db511bbd732e2860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 30 Oct 2025 12:41:07 +0100 Subject: [PATCH 34/34] runlogs 2025_10_30 --- contracts/contracts/interfaces/arm/IARM.sol | 223 +++++++++++++++ .../contracts/interfaces/arm/ISonicARM.sol | 267 ------------------ contracts/scripts/runlogs/2025_10.s.sol | 22 +- contracts/scripts/runlogs/utils/Addresses.sol | 10 +- contracts/scripts/runlogs/utils/Setup.sol | 25 +- 5 files changed, 265 insertions(+), 282 deletions(-) create mode 100644 contracts/contracts/interfaces/arm/IARM.sol delete mode 100644 contracts/contracts/interfaces/arm/ISonicARM.sol diff --git a/contracts/contracts/interfaces/arm/IARM.sol b/contracts/contracts/interfaces/arm/IARM.sol new file mode 100644 index 0000000000..359cb23a61 --- /dev/null +++ b/contracts/contracts/interfaces/arm/IARM.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface IARM { + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + error ERC20InvalidApprover(address approver); + error ERC20InvalidReceiver(address receiver); + error ERC20InvalidSender(address sender); + error ERC20InvalidSpender(address spender); + error InvalidInitialization(); + error NotInitializing(); + error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); + error SafeCastOverflowedIntToUint(int256 value); + error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); + error SafeCastOverflowedUintToInt(uint256 value); + + event ARMBufferUpdated(uint256 armBuffer); + event ActiveMarketUpdated(address indexed market); + event AdminChanged(address previousAdmin, address newAdmin); + event Allocated(address indexed market, int256 assets); + event Approval(address indexed owner, address indexed spender, uint256 value); + event CapManagerUpdated(address indexed capManager); + event ClaimOriginWithdrawals(uint256[] requestIds, uint256 amountClaimed); + event CrossPriceUpdated(uint256 crossPrice); + event Deposit(address indexed owner, uint256 assets, uint256 shares); + event FeeCollected(address indexed feeCollector, uint256 fee); + event FeeCollectorUpdated(address indexed newFeeCollector); + event FeeUpdated(uint256 fee); + event Initialized(uint64 version); + event MarketAdded(address indexed market); + event MarketRemoved(address indexed market); + event OperatorChanged(address newAdmin); + event RedeemClaimed(address indexed withdrawer, uint256 indexed requestId, uint256 assets); + event RedeemRequested( + address indexed withdrawer, + uint256 indexed requestId, + uint256 assets, + uint256 queued, + uint256 claimTimestamp + ); + event RequestOriginWithdrawal(uint256 amount, uint256 requestId); + event TraderateChanged(uint256 traderate0, uint256 traderate1); + event Transfer(address indexed from, address indexed to, uint256 value); + + function FEE_SCALE() external view returns (uint256); + + function MAX_CROSS_PRICE_DEVIATION() external view returns (uint256); + + function PRICE_SCALE() external view returns (uint256); + + function activeMarket() external view returns (address); + + function addMarkets(address[] memory _markets) external; + + function allocate() external returns (int256 liquidityDelta); + + function allocateThreshold() external view returns (int256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function armBuffer() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function baseAsset() external view returns (address); + + function capManager() external view returns (address); + + function claimDelay() external view returns (uint256); + + function claimOriginWithdrawals(uint256[] memory requestIds) + external + returns (uint256 amountClaimed); + + function claimRedeem(uint256 requestId) external returns (uint256 assets); + + function claimable() external view returns (uint256 claimableAmount); + + function collectFees() external returns (uint256 fees); + + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + function convertToShares(uint256 assets) external view returns (uint256 shares); + + function crossPrice() external view returns (uint256); + + function decimals() external view returns (uint8); + + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + function deposit(uint256 assets) external returns (uint256 shares); + + function fee() external view returns (uint16); + + function feeCollector() external view returns (address); + + function feesAccrued() external view returns (uint256 fees); + + function initialize( + string memory _name, + string memory _symbol, + address _operator, + uint256 _fee, + address _feeCollector, + address _capManager + ) external; + + function lastAvailableAssets() external view returns (int128); + + function liquidityAsset() external view returns (address); + + function minSharesToRedeem() external view returns (uint256); + + function name() external view returns (string memory); + + function nextWithdrawalIndex() external view returns (uint256); + + function operator() external view returns (address); + + function owner() external view returns (address); + + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + function removeMarket(address _market) external; + + function requestOriginWithdrawal(uint256 amount) external returns (uint256 requestId); + + function requestRedeem(uint256 shares) external returns (uint256 requestId, uint256 assets); + + function setARMBuffer(uint256 _armBuffer) external; + + function setActiveMarket(address _market) external; + + function setCapManager(address _capManager) external; + + function setCrossPrice(uint256 newCrossPrice) external; + + function setFee(uint256 _fee) external; + + function setFeeCollector(address _feeCollector) external; + + function setOperator(address newOperator) external; + + function setOwner(address newOwner) external; + + function setPrices(uint256 buyT1, uint256 sellT1) external; + + function supportedMarkets(address market) external view returns (bool supported); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] memory path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactTokensForTokens( + address inToken, + address outToken, + uint256 amountIn, + uint256 amountOutMin, + address to + ) external; + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] memory path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapTokensForExactTokens( + address inToken, + address outToken, + uint256 amountOut, + uint256 amountInMax, + address to + ) external; + + function symbol() external view returns (string memory); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function totalAssets() external view returns (uint256); + + function totalSupply() external view returns (uint256); + + function traderate0() external view returns (uint256); + + function traderate1() external view returns (uint256); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom(address from, address to, uint256 value) external returns (bool); + + function vault() external view returns (address); + + function vaultWithdrawalAmount() external view returns (uint256); + + function withdrawalRequests(uint256 requestId) + external + view + returns ( + address withdrawer, + bool claimed, + uint40 claimTimestamp, + uint128 assets, + uint128 queued + ); + + function withdrawsClaimed() external view returns (uint128); + + function withdrawsQueued() external view returns (uint128); +} diff --git a/contracts/contracts/interfaces/arm/ISonicARM.sol b/contracts/contracts/interfaces/arm/ISonicARM.sol deleted file mode 100644 index 5f166cc779..0000000000 --- a/contracts/contracts/interfaces/arm/ISonicARM.sol +++ /dev/null @@ -1,267 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.4; - -interface ISonicARM { - error ERC20InsufficientAllowance( - address spender, - uint256 allowance, - uint256 needed - ); - error ERC20InsufficientBalance( - address sender, - uint256 balance, - uint256 needed - ); - error ERC20InvalidApprover(address approver); - error ERC20InvalidReceiver(address receiver); - error ERC20InvalidSender(address sender); - error ERC20InvalidSpender(address spender); - error InvalidInitialization(); - error NotInitializing(); - error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); - error SafeCastOverflowedIntToUint(int256 value); - error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); - error SafeCastOverflowedUintToInt(uint256 value); - - event ARMBufferUpdated(uint256 armBuffer); - event ActiveMarketUpdated(address indexed market); - event AdminChanged(address previousAdmin, address newAdmin); - event Allocated(address indexed market, int256 assets); - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - event CapManagerUpdated(address indexed capManager); - event ClaimOriginWithdrawals(uint256[] requestIds, uint256 amountClaimed); - event CrossPriceUpdated(uint256 crossPrice); - event Deposit(address indexed owner, uint256 assets, uint256 shares); - event FeeCollected(address indexed feeCollector, uint256 fee); - event FeeCollectorUpdated(address indexed newFeeCollector); - event FeeUpdated(uint256 fee); - event Initialized(uint64 version); - event MarketAdded(address indexed market); - event MarketRemoved(address indexed market); - event OperatorChanged(address newAdmin); - event RedeemClaimed( - address indexed withdrawer, - uint256 indexed requestId, - uint256 assets - ); - event RedeemRequested( - address indexed withdrawer, - uint256 indexed requestId, - uint256 assets, - uint256 queued, - uint256 claimTimestamp - ); - event RequestOriginWithdrawal(uint256 amount, uint256 requestId); - event TraderateChanged(uint256 traderate0, uint256 traderate1); - event Transfer(address indexed from, address indexed to, uint256 value); - - function FEE_SCALE() external view returns (uint256); - - function MAX_CROSS_PRICE_DEVIATION() external view returns (uint256); - - function PRICE_SCALE() external view returns (uint256); - - function activeMarket() external view returns (address); - - function addMarkets(address[] memory _markets) external; - - function allocate() external returns (int256 liquidityDelta); - - function allocateThreshold() external view returns (int256); - - function allowance(address owner, address spender) - external - view - returns (uint256); - - function approve(address spender, uint256 value) external returns (bool); - - function armBuffer() external view returns (uint256); - - function balanceOf(address account) external view returns (uint256); - - function baseAsset() external view returns (address); - - function capManager() external view returns (address); - - function claimDelay() external view returns (uint256); - - function claimOriginWithdrawals(uint256[] memory requestIds) - external - returns (uint256 amountClaimed); - - function claimRedeem(uint256 requestId) external returns (uint256 assets); - - function claimable() external view returns (uint256 claimableAmount); - - function collectFees() external returns (uint256 fees); - - function convertToAssets(uint256 shares) - external - view - returns (uint256 assets); - - function convertToShares(uint256 assets) - external - view - returns (uint256 shares); - - function crossPrice() external view returns (uint256); - - function decimals() external view returns (uint8); - - function deposit(uint256 assets, address receiver) - external - returns (uint256 shares); - - function deposit(uint256 assets) external returns (uint256 shares); - - function fee() external view returns (uint16); - - function feeCollector() external view returns (address); - - function feesAccrued() external view returns (uint256 fees); - - function initialize( - string memory _name, - string memory _symbol, - address _operator, - uint256 _fee, - address _feeCollector, - address _capManager - ) external; - - function lastAvailableAssets() external view returns (int128); - - function liquidityAsset() external view returns (address); - - function minSharesToRedeem() external view returns (uint256); - - function name() external view returns (string memory); - - function nextWithdrawalIndex() external view returns (uint256); - - function operator() external view returns (address); - - function owner() external view returns (address); - - function previewDeposit(uint256 assets) - external - view - returns (uint256 shares); - - function previewRedeem(uint256 shares) - external - view - returns (uint256 assets); - - function removeMarket(address _market) external; - - function requestOriginWithdrawal(uint256 amount) - external - returns (uint256 requestId); - - function requestRedeem(uint256 shares) - external - returns (uint256 requestId, uint256 assets); - - function setARMBuffer(uint256 _armBuffer) external; - - function setActiveMarket(address _market) external; - - function setCapManager(address _capManager) external; - - function setCrossPrice(uint256 newCrossPrice) external; - - function setFee(uint256 _fee) external; - - function setFeeCollector(address _feeCollector) external; - - function setOperator(address newOperator) external; - - function setOwner(address newOwner) external; - - function setPrices(uint256 buyT1, uint256 sellT1) external; - - function supportedMarkets(address market) - external - view - returns (bool supported); - - function swapExactTokensForTokens( - uint256 amountIn, - uint256 amountOutMin, - address[] memory path, - address to, - uint256 deadline - ) external returns (uint256[] memory amounts); - - function swapExactTokensForTokens( - address inToken, - address outToken, - uint256 amountIn, - uint256 amountOutMin, - address to - ) external; - - function swapTokensForExactTokens( - uint256 amountOut, - uint256 amountInMax, - address[] memory path, - address to, - uint256 deadline - ) external returns (uint256[] memory amounts); - - function swapTokensForExactTokens( - address inToken, - address outToken, - uint256 amountOut, - uint256 amountInMax, - address to - ) external; - - function symbol() external view returns (string memory); - - function token0() external view returns (address); - - function token1() external view returns (address); - - function totalAssets() external view returns (uint256); - - function totalSupply() external view returns (uint256); - - function traderate0() external view returns (uint256); - - function traderate1() external view returns (uint256); - - function transfer(address to, uint256 value) external returns (bool); - - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool); - - function vault() external view returns (address); - - function vaultWithdrawalAmount() external view returns (uint256); - - function withdrawalRequests(uint256 requestId) - external - view - returns ( - address withdrawer, - bool claimed, - uint40 claimTimestamp, - uint128 assets, - uint128 queued - ); - - function withdrawsClaimed() external view returns (uint128); - - function withdrawsQueued() external view returns (uint128); -} diff --git a/contracts/scripts/runlogs/2025_10.s.sol b/contracts/scripts/runlogs/2025_10.s.sol index 79a86be0a0..1cd029b48d 100644 --- a/contracts/scripts/runlogs/2025_10.s.sol +++ b/contracts/scripts/runlogs/2025_10.s.sol @@ -13,8 +13,9 @@ import { console } from "forge-std/console.sol"; contract Runlogs_2025_10_Mainnet is SetupMainnet { function run() public { - _2025_10_01(); + //_2025_10_01(); //_2025_10_02(); + _2025_10_30(); } // ------------------------------------------------------------------ @@ -29,7 +30,7 @@ contract Runlogs_2025_10_Mainnet is SetupMainnet { console.log("-----"); console.log("strategist address", address(strategist)); console.log("poolBoosterFactoryMerkl address", address(poolBoosterFactoryMerkl)); - + address poolBoosterAddress = poolBoosterFactoryMerkl.computePoolBoosterAddress({ _campaignType: 45, _ammPoolAddress: CrossChain.MORPHO_BLUE, @@ -45,7 +46,7 @@ contract Runlogs_2025_10_Mainnet is SetupMainnet { vm.stopBroadcast(); } -// ------------------------------------------------------------------ + // ------------------------------------------------------------------ // Oct 3+ TODO, 2025 - Create Merkl Pool Booster once Central Registry governance passes // ------------------------------------------------------------------ function _2025_10_02() internal { @@ -64,4 +65,17 @@ contract Runlogs_2025_10_Mainnet is SetupMainnet { vm.stopBroadcast(); } -} \ No newline at end of file + + function _2025_10_30() internal { + // Amount to deposit into Etherfi ARM + uint256 amountToDeposit = 10 ether; + + vm.startBroadcast(treasury); + // Approve Etherfi ARM to pull WETH + weth.approve(address(etherfiARM), amountToDeposit); + + // Deposit WETH into Etherfi ARM + etherfiARM.deposit(amountToDeposit); + vm.stopBroadcast(); + } +} diff --git a/contracts/scripts/runlogs/utils/Addresses.sol b/contracts/scripts/runlogs/utils/Addresses.sol index 281dfdbe68..eaea31ed0d 100644 --- a/contracts/scripts/runlogs/utils/Addresses.sol +++ b/contracts/scripts/runlogs/utils/Addresses.sol @@ -14,6 +14,7 @@ library Mainnet { // Governance address public constant TIMELOCK = 0x35918cDE7233F2dD33fA41ae3Cb6aE0e42E0e69F; + address public constant TREASURY = 0x6E3fddab68Bf1EBaf9daCF9F7907c7Bc0951D1dc; // OUSD address public constant OUSD = 0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86; @@ -29,7 +30,8 @@ library Mainnet { // Pool Booster address public constant POOL_BOOSTER_FACTORY_MERKL = 0x0FC66355B681503eFeE7741BD848080d809FD6db; - address public constant POOL_BOOSTER_CENTRAL_REGISTRY = 0xAA8af8Db4B6a827B51786334d26349eb03569731; + address public constant POOL_BOOSTER_CENTRAL_REGISTRY = + 0xAA8af8Db4B6a827B51786334d26349eb03569731; // Other token address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; @@ -37,6 +39,9 @@ library Mainnet { // Curve pools address public constant OETH_WETH_CURVE_POOL = 0xcc7d5785AD5755B6164e21495E07aDb0Ff11C2A8; + + // ARMs + address public constant ETHERFI_ARM = 0xfB0A3CF9B019BFd8827443d131b235B3E0FC58d2; } library Base { @@ -57,7 +62,8 @@ library Base { // Pool Booster address public constant POOL_BOOSTER_FACTORY_MERKL = 0x1ADB902Ece465cA681C66187627a622a631a0a63; - address public constant POOL_BOOSTER_CENTRAL_REGISTRY = 0x157f0B239D7F83D153E6c95F8AD9d341694376E3; + address public constant POOL_BOOSTER_CENTRAL_REGISTRY = + 0x157f0B239D7F83D153E6c95F8AD9d341694376E3; // Other token address public constant WETH = 0x4200000000000000000000000000000000000006; diff --git a/contracts/scripts/runlogs/utils/Setup.sol b/contracts/scripts/runlogs/utils/Setup.sol index cd09a4d079..1ab910ee24 100644 --- a/contracts/scripts/runlogs/utils/Setup.sol +++ b/contracts/scripts/runlogs/utils/Setup.sol @@ -23,18 +23,23 @@ import { OSonic } from "contracts/contracts/token/OSonic.sol"; // Contracts - Strategies import { CurveAMOStrategy } from "contracts/contracts/strategies/CurveAMOStrategy.sol"; import { BaseCurveAMOStrategy } from "contracts/contracts/strategies/BaseCurveAMOStrategy.sol"; -import { SonicStakingStrategy } from "contracts/contracts/strategies/sonic/SonicStakingStrategy.sol"; -import { AerodromeAMOStrategy } from - "contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol"; +import { + SonicStakingStrategy +} from "contracts/contracts/strategies/sonic/SonicStakingStrategy.sol"; +import { + AerodromeAMOStrategy +} from "contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol"; // Contracts - ARM -import { ISonicARM } from "contracts/contracts/interfaces/arm/ISonicARM.sol"; +import { IARM } from "contracts/contracts/interfaces/arm/IARM.sol"; // Contracts - Pool Booster -import { PoolBoosterFactoryMerkl } from - "contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol"; -import { PoolBoostCentralRegistry } from - "contracts/contracts/poolBooster/PoolBoostCentralRegistry.sol"; +import { + PoolBoosterFactoryMerkl +} from "contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol"; +import { + PoolBoostCentralRegistry +} from "contracts/contracts/poolBooster/PoolBoostCentralRegistry.sol"; // Interfaces import { IWETH9 } from "contracts/contracts/interfaces/IWETH9.sol"; @@ -50,6 +55,7 @@ import { Test } from "forge-std/Test.sol"; abstract contract SetupMainnet is Test, Script { // Governance address public strategist = CrossChain.STRATEGIST; + address public treasury = Mainnet.TREASURY; // OUSD OUSD public ousd = OUSD(Mainnet.OUSD); @@ -72,6 +78,7 @@ abstract contract SetupMainnet is Test, Script { // Interfaces IWETH9 public weth = IWETH9(Mainnet.WETH); ICurveStableSwapNG public oethWethCurvePool = ICurveStableSwapNG(Mainnet.OETH_WETH_CURVE_POOL); + IARM public etherfiARM = IARM(Mainnet.ETHERFI_ARM); function setUp() public { // Note: to ensure perfect simulation, don't fix block number, it will be automatically set to the latest block @@ -128,7 +135,7 @@ abstract contract SetupSonic is Test, Script { IWETH9 public ws = IWETH9(Sonic.WS); // ARM - ISonicARM public arm = ISonicARM(Sonic.ARM); + IARM public arm = IARM(Sonic.ARM); // Staking strategy SonicStakingStrategy public stakingStrategy =