From 21f73d7f4d31dd626aec345926d405288e6017c9 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 22 May 2024 16:11:13 +0300 Subject: [PATCH 01/57] Add farm proxies --- .gitignore | 14 +++++ .gitmodules | 3 + README.md | 1 + foundry.toml | 7 +++ lib/dss-test | 1 + src/L1StakingRewardProxy.sol | 107 +++++++++++++++++++++++++++++++++++ src/L2StakingRewardProxy.sol | 68 ++++++++++++++++++++++ 7 files changed, 201 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 README.md create mode 100644 foundry.toml create mode 160000 lib/dss-test create mode 100644 src/L1StakingRewardProxy.sol create mode 100644 src/L2StakingRewardProxy.sol diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deea2d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a2df3f1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/dss-test"] + path = lib/dss-test + url = https://github.com/makerdao/dss-test diff --git a/README.md b/README.md new file mode 100644 index 0000000..8722a27 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Endgame L2 Farms diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..4d52ce3 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,7 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc = "0.8.21" + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/dss-test b/lib/dss-test new file mode 160000 index 0000000..41066f6 --- /dev/null +++ b/lib/dss-test @@ -0,0 +1 @@ +Subproject commit 41066f6d18202c61208d8cf09b38532a6f5b0d0a diff --git a/src/L1StakingRewardProxy.sol b/src/L1StakingRewardProxy.sol new file mode 100644 index 0000000..d28e2d1 --- /dev/null +++ b/src/L1StakingRewardProxy.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +interface L1TokenGatewayLike { + function outboundTransferCustomRefund( + address l1Token, + address to, + address refundTo, + uint256 amount, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (bytes memory); +} + +interface InboxLike { + function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) external view returns (uint256); +} + +contract L1StakingRewardProxy { + mapping (address => uint256) public wards; + uint64 public maxGas; // TODO: figure out reasonable default for arbitrum-one + uint192 public gasPriceBid = 0.1 gwei; // 0.01 gwei arbitrum-one gas price floor * 10x factor + + address public immutable gem; + address public immutable l2Proxy; + address public immutable feeRecipient; + InboxLike public immutable inbox; + L1TokenGatewayLike public immutable gateway; + + event Rely(address indexed usr); + event Deny(address indexed usr); + event File(bytes32 indexed what, uint256 data); + + constructor(address _gem, address _l2Proxy, address _feeRecipient, address _inbox, address _gateway) { + gem = _gem; + l2Proxy = _l2Proxy; + feeRecipient = _feeRecipient; + inbox = InboxLike(_inbox); + gateway = L1TokenGatewayLike(_gateway); + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + modifier auth { + require(wards[msg.sender] == 1, "L1StakingRewardProxy/not-authorized"); + _; + } + + function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } + function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } + + function file(bytes32 what, uint256 data) external auth { + if (what == "maxGas") maxGas = uint64(data); + else if (what == "gasPriceBid") gasPriceBid = uint192(data); + else revert("L1StakingRewardProxy/file-unrecognized-param"); + emit File(what, data); + } + + // @notice Allow contract to receive ether + receive() external payable {} + + // @notice Allow governance to reclaim stored ether + function reclaim(address receiver, uint256 amount) external auth { + (bool sent,) = receiver.call{value: amount}(""); + require(sent, "L1StakingRewardProxy/failed-to-send-ether"); + } + + // @notice As this function is permissionless, it could in theory be called at a time where + // maxGas and/or gasPriceBid are too low for the auto-redeem of the gem deposit RetryableTicket. + // This is mitigated by incorporating large enough safety factors in maxGas and gasPriceBid. + // Note that in any case a failed auto-redeem can be permissonlessly retried for 7 days + function notifyRewardAmount(uint256 reward) external { + require(reward > 0, "L1StakingRewardProxy/no-reward"); // prevent wasting gas for no-op + + (uint256 maxGas_, uint256 gasPriceBid_) = (maxGas, gasPriceBid); + uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes + uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; + + gateway.outboundTransferCustomRefund{value: l1CallValue}({ + l1Token: gem, + to: l2Proxy, + refundTo: feeRecipient, + amount: reward, + maxGas: maxGas_, + gasPriceBid: gasPriceBid_, + data: abi.encode(maxSubmissionCost, bytes("")) + }); + } +} \ No newline at end of file diff --git a/src/L2StakingRewardProxy.sol b/src/L2StakingRewardProxy.sol new file mode 100644 index 0000000..fbda220 --- /dev/null +++ b/src/L2StakingRewardProxy.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +interface FarmLike { + function notifyRewardAmount(uint256 reward) external; +} + +interface GemLike { + function transfer(address, uint256) external; +} + +contract L2StakingRewardProxy { + mapping (address => uint256) public wards; + uint256 public minReward; + + GemLike public immutable gem; + FarmLike public immutable farm; + + event Rely(address indexed usr); + event Deny(address indexed usr); + event File(bytes32 indexed what, uint256 data); + + constructor(address _gem, address _farm) { + gem = GemLike(_gem); + farm = FarmLike(_farm); + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + modifier auth { + require(wards[msg.sender] == 1, "L2StakingRewardProxy/not-authorized"); + _; + } + + function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } + function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } + + function file(bytes32 what, uint256 data) external auth { + if (what == "minReward") minReward = data; + else revert("L2StakingRewardProxy/file-unrecognized-param"); + emit File(what, data); + } + + // @notice `reward` must exceed a minimum threshold to reduce the impact of calling + // this function too frequently in an attempt to reduce the rewardRate of the farm + function notifyRewardAmount(uint256 reward) external { + require(reward >= minReward, "L2StakingRewardProxy/reward-too-small"); + gem.transfer(address(farm), reward); + farm.notifyRewardAmount(reward); + } +} \ No newline at end of file From c8febe2429209c2884aa295ec160b091ac97c279 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 22 May 2024 16:23:36 +0300 Subject: [PATCH 02/57] Add missing approval --- src/L1StakingRewardProxy.sol | 6 ++++++ src/L2StakingRewardProxy.sol | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/L1StakingRewardProxy.sol b/src/L1StakingRewardProxy.sol index d28e2d1..5e8dc2f 100644 --- a/src/L1StakingRewardProxy.sol +++ b/src/L1StakingRewardProxy.sol @@ -17,6 +17,10 @@ pragma solidity ^0.8.21; +interface GemLike { + function approve(address, uint256) external; +} + interface L1TokenGatewayLike { function outboundTransferCustomRefund( address l1Token, @@ -55,6 +59,8 @@ contract L1StakingRewardProxy { inbox = InboxLike(_inbox); gateway = L1TokenGatewayLike(_gateway); + GemLike(_gem).approve(_gateway, type(uint256).max); + wards[msg.sender] = 1; emit Rely(msg.sender); } diff --git a/src/L2StakingRewardProxy.sol b/src/L2StakingRewardProxy.sol index fbda220..aa04033 100644 --- a/src/L2StakingRewardProxy.sol +++ b/src/L2StakingRewardProxy.sol @@ -18,6 +18,7 @@ pragma solidity ^0.8.21; interface FarmLike { + function rewardsToken() external view returns (address); function notifyRewardAmount(uint256 reward) external; } @@ -36,9 +37,9 @@ contract L2StakingRewardProxy { event Deny(address indexed usr); event File(bytes32 indexed what, uint256 data); - constructor(address _gem, address _farm) { - gem = GemLike(_gem); + constructor(address _farm) { farm = FarmLike(_farm); + gem = GemLike(farm.rewardsToken()); wards[msg.sender] = 1; emit Rely(msg.sender); From afb473c2b5feb525e380995988ced85abd07e839 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 24 May 2024 16:52:53 +0300 Subject: [PATCH 03/57] Add e2e test & deploy scripts --- .gitmodules | 6 + deploy/FarmProxyDeploy.sol | 49 ++++ deploy/FarmProxyInit.sol | 94 ++++++ deploy/L2FarmProxyInstance.sol | 22 ++ deploy/L2FarmProxySpell.sol | 44 +++ foundry.toml | 9 +- lib/endgame-toolkit | 1 + lib/new-bridge | 1 + remappings.txt | 2 + script/input/1/config.json | 25 ++ ...StakingRewardProxy.sol => L1FarmProxy.sol} | 32 +-- ...StakingRewardProxy.sol => L2FarmProxy.sol} | 22 +- test/Integration.t.sol | 270 ++++++++++++++++++ 13 files changed, 550 insertions(+), 27 deletions(-) create mode 100644 deploy/FarmProxyDeploy.sol create mode 100644 deploy/FarmProxyInit.sol create mode 100644 deploy/L2FarmProxyInstance.sol create mode 100644 deploy/L2FarmProxySpell.sol create mode 160000 lib/endgame-toolkit create mode 160000 lib/new-bridge create mode 100644 remappings.txt create mode 100644 script/input/1/config.json rename src/{L1StakingRewardProxy.sol => L1FarmProxy.sol} (80%) rename src/{L2StakingRewardProxy.sol => L2FarmProxy.sol} (70%) create mode 100644 test/Integration.t.sol diff --git a/.gitmodules b/.gitmodules index a2df3f1..f47e6d0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "lib/dss-test"] path = lib/dss-test url = https://github.com/makerdao/dss-test +[submodule "lib/new-bridge"] + path = lib/new-bridge + url = https://github.com/telome/new-bridge +[submodule "lib/endgame-toolkit"] + path = lib/endgame-toolkit + url = https://github.com/makerdao/endgame-toolkit diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol new file mode 100644 index 0000000..e7c5468 --- /dev/null +++ b/deploy/FarmProxyDeploy.sol @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: © 2024 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; + +import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; +import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; +import { L1FarmProxy } from "src/L1FarmProxy.sol"; +import { L2FarmProxy } from "src/L2FarmProxy.sol"; + +library FarmProxyDeploy { + function deployL1Proxy( + address deployer, + address owner, + address gem, + address l2Proxy, + address feeRecipient, + address inbox, + address l1Gateway + ) internal returns (address l1Proxy) { + l1Proxy = address(new L1FarmProxy(gem, l2Proxy, feeRecipient, inbox, l1Gateway)); + ScriptTools.switchOwner(l1Proxy, deployer, owner); + } + + function deployL2Proxy( + address deployer, + address owner, + address farm + ) internal returns (L2FarmProxyInstance memory l2ProxyInstance) { + l2ProxyInstance.proxy = address(new L2FarmProxy(farm)); + l2ProxyInstance.spell = address(new L2FarmProxySpell(l2ProxyInstance.proxy)); + ScriptTools.switchOwner(l2ProxyInstance.proxy, deployer, owner); + } +} diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol new file mode 100644 index 0000000..c8aca32 --- /dev/null +++ b/deploy/FarmProxyInit.sol @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: © 2024 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import { DssInstance } from "dss-test/MCD.sol"; +import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; +import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; + +interface L1FarmProxyLike { + function rewardsToken() external view returns (address); + function l2Proxy() external view returns (address); + function feeRecipient() external view returns (address); + function inbox() external view returns (address); + function l1Gateway() external view returns (address); +} + +interface L1RelayLike { + function relay( + address target, + bytes calldata targetData, + uint256 l1CallValue, + uint256 maxGas, + uint256 gasPriceBid, + uint256 maxSubmissionCost + ) external payable; +} + +struct MessageParams { + uint256 maxGas; + uint256 gasPriceBid; + uint256 maxSubmissionCost; +} + +struct ProxiesConfig { + address rewardsToken; + address l2Proxy; + address feeRecipient; + address inbox; + address l1Gateway; + uint256 minReward; + uint256 rewardsDuration; + MessageParams xchainMsg; +} + +library FarmProxyInit { + function initProxies( + DssInstance memory dss, + address l1Proxy_, + L2FarmProxyInstance memory l2ProxyInstance, + ProxiesConfig memory cfg + ) internal { + L1FarmProxyLike l1Proxy = L1FarmProxyLike(l1Proxy_); + + // sanity checks + require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/gem-mismatch"); + require(l1Proxy.l2Proxy() == cfg.l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); + require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); + require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); + + L1RelayLike l1GovRelay = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")); + + uint256 l1CallValue = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; + + // not strictly necessary (as the retryable ticket creation would otherwise fail) + // but makes the eth balance requirement more explicit + require(address(l1GovRelay).balance >= l1CallValue, "FarmProxyInit/insufficient-relay-balance"); + + l1GovRelay.relay({ + target: l2ProxyInstance.spell, + targetData: abi.encodeCall(L2FarmProxySpell.init, (cfg.minReward, cfg.rewardsDuration)), + l1CallValue: l1CallValue, + maxGas: cfg.xchainMsg.maxGas, + gasPriceBid: cfg.xchainMsg.gasPriceBid, + maxSubmissionCost: cfg.xchainMsg.maxSubmissionCost + }); + + dss.chainlog.setAddress("ARBITRUM_L1_FARM_PROXY", l1Proxy_); + } +} diff --git a/deploy/L2FarmProxyInstance.sol b/deploy/L2FarmProxyInstance.sol new file mode 100644 index 0000000..4aeffba --- /dev/null +++ b/deploy/L2FarmProxyInstance.sol @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: © 2024 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +struct L2FarmProxyInstance { + address proxy; + address spell; +} diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol new file mode 100644 index 0000000..d738294 --- /dev/null +++ b/deploy/L2FarmProxySpell.sol @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: © 2024 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import { L2FarmProxy } from "src/L2FarmProxy.sol"; + +interface FarmLike { + function setRewardsDistribution(address) external; + function setRewardsDuration(uint256) external; +} + +// A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2FarmProxy +contract L2FarmProxySpell { + L2FarmProxy public immutable l2Proxy; + constructor(address l2Proxy_) { + l2Proxy = L2FarmProxy(l2Proxy_); + } + + function rely(address usr) external { l2Proxy.rely(usr); } + function deny(address usr) external { l2Proxy.deny(usr); } + function file(bytes32 what, uint256 data) external { l2Proxy.file(what, data); } + + function init(uint256 minReward, uint256 rewardsDuration) external { + l2Proxy.file("minReward", minReward); + + FarmLike farm = FarmLike(address(l2Proxy.farm())); + farm.setRewardsDistribution(address(l2Proxy)); + farm.setRewardsDuration(rewardsDuration); + } +} diff --git a/foundry.toml b/foundry.toml index 4d52ce3..7547d01 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,5 +3,12 @@ src = "src" out = "out" libs = ["lib"] solc = "0.8.21" +fs_permissions = [ + { access = "read", path = "./script/input/"} +] -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +[etherscan] +mainnet = { key = "${ETHERSCAN_KEY}" } +sepolia = { key = "${ETHERSCAN_KEY}", chain = 11155111 } +arbitrum_one = { key = "${ARBISCAN_KEY}", chain = 42161, url = "https://api.arbiscan.io/api" } +arbitrum_one_sepolia = { key = "${ARBISCAN_KEY}", chain = 421614, url = "https://api-sepolia.arbiscan.io/api" } \ No newline at end of file diff --git a/lib/endgame-toolkit b/lib/endgame-toolkit new file mode 160000 index 0000000..8c879af --- /dev/null +++ b/lib/endgame-toolkit @@ -0,0 +1 @@ +Subproject commit 8c879af2c5a4a666995d8f5e20647a128d03fb0a diff --git a/lib/new-bridge b/lib/new-bridge new file mode 160000 index 0000000..7b592a4 --- /dev/null +++ b/lib/new-bridge @@ -0,0 +1 @@ +Subproject commit 7b592a4e1fb7dbd1e0c6a0fa4ef801415859abe1 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..2061cc3 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,2 @@ +forge-std/=lib/dss-test/lib/forge-std/src/ +openzeppelin-contracts/=lib/endgame-toolkit/lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/script/input/1/config.json b/script/input/1/config.json new file mode 100644 index 0000000..56187f5 --- /dev/null +++ b/script/input/1/config.json @@ -0,0 +1,25 @@ +{ + "domains": { + "mainnet": { + "type": "root", + "rpc": "ETH_RPC_URL", + "chainlog": "0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F" + }, + "arbitrum_one": { + "type": "arbitrum", + "rpc": "ARB_RPC_URL", + "inbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", + "arbSys": "0x0000000000000000000000000000000000000064" + }, + "sepolia": { + "type": "root", + "rpc": "SEPOLIA_RPC_URL" + }, + "arbitrum_one_sepolia": { + "type": "arbitrum", + "rpc": "ARBITRUM_ONE_SEPOLIA_RPC_URL", + "inbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", + "arbSys": "0x0000000000000000000000000000000000000064" + } + } +} diff --git a/src/L1StakingRewardProxy.sol b/src/L1FarmProxy.sol similarity index 80% rename from src/L1StakingRewardProxy.sol rename to src/L1FarmProxy.sol index 5e8dc2f..b898ece 100644 --- a/src/L1StakingRewardProxy.sol +++ b/src/L1FarmProxy.sol @@ -24,8 +24,8 @@ interface GemLike { interface L1TokenGatewayLike { function outboundTransferCustomRefund( address l1Token, - address to, address refundTo, + address to, uint256 amount, uint256 maxGas, uint256 gasPriceBid, @@ -37,36 +37,36 @@ interface InboxLike { function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) external view returns (uint256); } -contract L1StakingRewardProxy { +contract L1FarmProxy { mapping (address => uint256) public wards; - uint64 public maxGas; // TODO: figure out reasonable default for arbitrum-one + uint64 public maxGas = 300_000; // TODO: figure out reasonable default for arbitrum-one uint192 public gasPriceBid = 0.1 gwei; // 0.01 gwei arbitrum-one gas price floor * 10x factor - address public immutable gem; + address public immutable rewardsToken; address public immutable l2Proxy; address public immutable feeRecipient; InboxLike public immutable inbox; - L1TokenGatewayLike public immutable gateway; + L1TokenGatewayLike public immutable l1Gateway; event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, uint256 data); - constructor(address _gem, address _l2Proxy, address _feeRecipient, address _inbox, address _gateway) { - gem = _gem; + constructor(address _rewardsToken, address _l2Proxy, address _feeRecipient, address _inbox, address _l1Gateway) { + rewardsToken = _rewardsToken; l2Proxy = _l2Proxy; feeRecipient = _feeRecipient; inbox = InboxLike(_inbox); - gateway = L1TokenGatewayLike(_gateway); + l1Gateway = L1TokenGatewayLike(_l1Gateway); - GemLike(_gem).approve(_gateway, type(uint256).max); + GemLike(_rewardsToken).approve(_l1Gateway, type(uint256).max); wards[msg.sender] = 1; emit Rely(msg.sender); } modifier auth { - require(wards[msg.sender] == 1, "L1StakingRewardProxy/not-authorized"); + require(wards[msg.sender] == 1, "L1FarmProxy/not-authorized"); _; } @@ -76,7 +76,7 @@ contract L1StakingRewardProxy { function file(bytes32 what, uint256 data) external auth { if (what == "maxGas") maxGas = uint64(data); else if (what == "gasPriceBid") gasPriceBid = uint192(data); - else revert("L1StakingRewardProxy/file-unrecognized-param"); + else revert("L1FarmProxy/file-unrecognized-param"); emit File(what, data); } @@ -86,7 +86,7 @@ contract L1StakingRewardProxy { // @notice Allow governance to reclaim stored ether function reclaim(address receiver, uint256 amount) external auth { (bool sent,) = receiver.call{value: amount}(""); - require(sent, "L1StakingRewardProxy/failed-to-send-ether"); + require(sent, "L1FarmProxy/failed-to-send-ether"); } // @notice As this function is permissionless, it could in theory be called at a time where @@ -94,16 +94,16 @@ contract L1StakingRewardProxy { // This is mitigated by incorporating large enough safety factors in maxGas and gasPriceBid. // Note that in any case a failed auto-redeem can be permissonlessly retried for 7 days function notifyRewardAmount(uint256 reward) external { - require(reward > 0, "L1StakingRewardProxy/no-reward"); // prevent wasting gas for no-op + require(reward > 0, "L1FarmProxy/no-reward"); // prevent wasting gas for no-op (uint256 maxGas_, uint256 gasPriceBid_) = (maxGas, gasPriceBid); uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; - gateway.outboundTransferCustomRefund{value: l1CallValue}({ - l1Token: gem, - to: l2Proxy, + l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ + l1Token: rewardsToken, refundTo: feeRecipient, + to: l2Proxy, amount: reward, maxGas: maxGas_, gasPriceBid: gasPriceBid_, diff --git a/src/L2StakingRewardProxy.sol b/src/L2FarmProxy.sol similarity index 70% rename from src/L2StakingRewardProxy.sol rename to src/L2FarmProxy.sol index aa04033..103573f 100644 --- a/src/L2StakingRewardProxy.sol +++ b/src/L2FarmProxy.sol @@ -23,14 +23,15 @@ interface FarmLike { } interface GemLike { + function balanceOf(address) external view returns (uint256); function transfer(address, uint256) external; } -contract L2StakingRewardProxy { +contract L2FarmProxy { mapping (address => uint256) public wards; uint256 public minReward; - GemLike public immutable gem; + GemLike public immutable rewardsToken; FarmLike public immutable farm; event Rely(address indexed usr); @@ -39,14 +40,14 @@ contract L2StakingRewardProxy { constructor(address _farm) { farm = FarmLike(_farm); - gem = GemLike(farm.rewardsToken()); + rewardsToken = GemLike(farm.rewardsToken()); wards[msg.sender] = 1; emit Rely(msg.sender); } modifier auth { - require(wards[msg.sender] == 1, "L2StakingRewardProxy/not-authorized"); + require(wards[msg.sender] == 1, "L2FarmProxy/not-authorized"); _; } @@ -55,15 +56,16 @@ contract L2StakingRewardProxy { function file(bytes32 what, uint256 data) external auth { if (what == "minReward") minReward = data; - else revert("L2StakingRewardProxy/file-unrecognized-param"); + else revert("L2FarmProxy/file-unrecognized-param"); emit File(what, data); } - // @notice `reward` must exceed a minimum threshold to reduce the impact of calling - // this function too frequently in an attempt to reduce the rewardRate of the farm - function notifyRewardAmount(uint256 reward) external { - require(reward >= minReward, "L2StakingRewardProxy/reward-too-small"); - gem.transfer(address(farm), reward); + // @notice The transferred reward must exceed a minimum threshold to reduce the impact of + // calling this function too frequently in an attempt to reduce the rewardRate of the farm + function forwardReward() external { + uint256 reward = rewardsToken.balanceOf(address(this)); + require(reward >= minReward, "L2FarmProxy/reward-too-small"); + rewardsToken.transfer(address(farm), reward); farm.notifyRewardAmount(reward); } } \ No newline at end of file diff --git a/test/Integration.t.sol b/test/Integration.t.sol new file mode 100644 index 0000000..fdc8bf5 --- /dev/null +++ b/test/Integration.t.sol @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; + +import { Domain } from "dss-test/domains/Domain.sol"; +import { ArbitrumDomain } from "dss-test/domains/ArbitrumDomain.sol"; + +import { TokenGatewayDeploy } from "lib/new-bridge/deploy/TokenGatewayDeploy.sol"; +import { L2TokenGatewaySpell } from "lib/new-bridge/deploy/L2TokenGatewaySpell.sol"; +import { L2TokenGatewayInstance } from "lib/new-bridge/deploy/L2TokenGatewayInstance.sol"; +import { TokenGatewayInit, GatewaysConfig, MessageParams as GatewayMessageParams } from "lib/new-bridge/deploy/TokenGatewayInit.sol"; +import { GemMock } from "lib/new-bridge/test/mocks/GemMock.sol"; + +import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; +import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; +import { VestedRewardsDistributionInit, VestedRewardsDistributionInitParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionInit.sol"; +import { VestedRewardsDistribution } from "lib/endgame-toolkit/src/VestedRewardsDistribution.sol"; + +import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; +import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; +import { L2FarmProxyInstance } from "deploy/L2FarmProxyInstance.sol"; +import { FarmProxyInit, ProxiesConfig, MessageParams as ProxyMessageParams } from "deploy/FarmProxyInit.sol"; +import { L1FarmProxy } from "src/L1FarmProxy.sol"; +import { L2FarmProxy } from "src/L2FarmProxy.sol"; + +interface L1RelayLike { + function l2GovernanceRelay() external view returns (address); +} + +interface GemLike { + function balanceOf(address) external view returns (uint256); +} + +interface DssVestLike { + function bgn(uint256) external view returns (uint256); + function fin(uint256) external view returns (uint256); + function tot(uint256) external view returns (uint256); + function unpaid(uint256 _id) external view returns (uint256); + function create(address _usr, uint256 _tot, uint256 _bgn, uint256 _tau, uint256 _eta, address _mgr) external returns (uint256 id); + function restrict(uint256 _id) external; +} + +contract IntegrationTest is DssTest { + string config; + Domain l1Domain; + ArbitrumDomain l2Domain; + + // L1-side + DssInstance dss; + address PAUSE_PROXY; + address ESCROW; + address l1Token; + address l1Gateway; + address INBOX; + address l1Proxy; + DssVestLike vest; + uint256 vestId; + VestedRewardsDistribution vestedRewardDistribution; + + // L2-side + address L2_GOV_RELAY; + GemMock l2Token; + address l2Gateway; + L2FarmProxy l2Proxy; + StakingRewards farm; + + function setupGateways() internal { + ESCROW = dss.chainlog.getAddress("ARBITRUM_ESCROW"); + vm.label(address(ESCROW), "ESCROW"); + + l2Domain = new ArbitrumDomain(config, getChain("arbitrum_one"), l1Domain); + INBOX = l2Domain.readConfigAddress("inbox"); + vm.label(INBOX, "INBOX"); + + address l1Gateway_ = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 2); // foundry increments a global nonce across domains + l2Domain.selectFork(); + L2TokenGatewayInstance memory l2GatewayInstance = TokenGatewayDeploy.deployL2Gateway({ + deployer: address(this), + owner: L2_GOV_RELAY, + l1Gateway: l1Gateway_, + l2Router: address(0) + }); + l2Gateway = l2GatewayInstance.gateway; + assertEq(address(L2TokenGatewaySpell(l2GatewayInstance.spell).l2Gateway()), address(l2Gateway)); + + l1Domain.selectFork(); + l1Gateway = TokenGatewayDeploy.deployL1Gateway({ + deployer: address(this), + owner: PAUSE_PROXY, + l2Gateway: address(l2Gateway), + l1Router: address(0), + inbox: INBOX, + escrow: ESCROW + }); + assertEq(address(l1Gateway), l1Gateway_); + + l2Domain.selectFork(); + l2Token = new GemMock(0); + l2Token.rely(L2_GOV_RELAY); + l2Token.deny(address(this)); + vm.label(address(l2Token), "l2Token"); + + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = l1Token; + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = address(l2Token); + GatewayMessageParams memory xchainMsg = GatewayMessageParams({ + gasPriceBid: 0.1 gwei, + maxGas: 300_000, + maxSubmissionCost: 0.01 ether + }); + GatewaysConfig memory cfg = GatewaysConfig({ + counterpartGateway: address(l2Gateway), + l1Router: address(0), + inbox: INBOX, + l1Tokens: l1Tokens, + l2Tokens: l2Tokens, + xchainMsg: xchainMsg + }); + + l1Domain.selectFork(); + vm.startPrank(PAUSE_PROXY); + TokenGatewayInit.initGateways(dss, address(l1Gateway), l2GatewayInstance, cfg); + vm.stopPrank(); + } + + function setUp() public { + config = ScriptTools.readInput("config"); + + l1Domain = new Domain(config, getChain("mainnet")); + l1Domain.selectFork(); + l1Domain.loadDssFromChainlog(); + dss = l1Domain.dss(); + PAUSE_PROXY = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + L2_GOV_RELAY = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")).l2GovernanceRelay(); + l1Token = dss.chainlog.getAddress("MCD_GOV"); // TODO: change to use NGT when deployed + vm.label(address(PAUSE_PROXY), "PAUSE_PROXY"); + vm.label(address(L2_GOV_RELAY), "L2_GOV_RELAY"); + vm.label(l1Token, "l1Token"); + + setupGateways(); + + l2Domain.selectFork(); + + StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ + owner: L2_GOV_RELAY, + stakingToken: address(new GemMock(100 ether)), + rewardsToken: address(l2Token) + }); + farm = StakingRewards(StakingRewardsDeploy.deploy(farmParams)); + + L2FarmProxyInstance memory l2ProxyInstance = FarmProxyDeploy.deployL2Proxy({ + deployer: address(this), + owner: L2_GOV_RELAY, + farm: address(farm) + }); + l2Proxy = L2FarmProxy(l2ProxyInstance.proxy); + assertEq(address(L2FarmProxySpell(l2ProxyInstance.spell).l2Proxy()), address(l2Proxy)); + + l1Domain.selectFork(); + l1Proxy = FarmProxyDeploy.deployL1Proxy({ + deployer: address(this), + owner: PAUSE_PROXY, + gem: l1Token, + l2Proxy: address(l2Proxy), + feeRecipient: L2_GOV_RELAY, + inbox: INBOX, + l1Gateway: l1Gateway + }); + (bool success,) = l1Proxy.call{value: 1 ether}(""); // not using deal() here, so as to check payable fallback + assertTrue(success); + + ProxyMessageParams memory xchainMsg = ProxyMessageParams({ + gasPriceBid: 0.1 gwei, + maxGas: 300_000, + maxSubmissionCost: 0.01 ether + }); + ProxiesConfig memory cfg = ProxiesConfig({ + rewardsToken: l1Token, + l2Proxy: address(l2Proxy), + feeRecipient: L2_GOV_RELAY, + inbox: INBOX, + l1Gateway: l1Gateway, + minReward: 1 ether, + rewardsDuration: 1 days, + xchainMsg: xchainMsg + }); + vm.startPrank(PAUSE_PROXY); + FarmProxyInit.initProxies(dss, l1Proxy, l2ProxyInstance, cfg); + vm.stopPrank(); + + // test L1 side of initProxies + assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), l1Proxy); + + l2Domain.relayFromHost(true); + + // test L2 side of initProxies + assertEq(l2Proxy.minReward(), cfg.minReward); + assertEq(farm.rewardsDistribution(), address(l2Proxy)); + assertEq(farm.rewardsDuration(), cfg.rewardsDuration); + + l1Domain.selectFork(); + vest = DssVestLike(dss.chainlog.getAddress("MCD_VEST_MKR")); // TODO: change to use NGT vest when deployed + VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ + deployer: address(this), + owner: PAUSE_PROXY, + vest: address(vest), + rewards: l1Proxy + }); + vestedRewardDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams)); + + vm.startPrank(PAUSE_PROXY); + vestId = vest.create({ + _usr: address(vestedRewardDistribution), + _tot: 100 * 1e18, + _bgn: block.timestamp, + _tau: 100 days, + _eta: 0, + _mgr: address(0) + }); + vest.restrict(vestId); + VestedRewardsDistributionInitParams memory distributionInitParams = VestedRewardsDistributionInitParams({ + vestId: vestId + }); + VestedRewardsDistributionInit.init(address(vestedRewardDistribution), distributionInitParams); + vm.stopPrank(); + } + + function testDistribution() public { + l2Domain.selectFork(); + uint256 minReward = l2Proxy.minReward(); + + l1Domain.selectFork(); + vm.warp(vest.bgn(vestId) + minReward * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId)); + uint256 amount = vest.unpaid(vestId); + assertGe(amount, minReward); + assertEq(GemLike(l1Token).balanceOf(ESCROW), 0); + + vestedRewardDistribution.distribute(); + + assertEq(GemLike(l1Token).balanceOf(ESCROW), amount); + + l2Domain.relayFromHost(true); + + assertEq(l2Token.balanceOf(address(l2Proxy)), amount); + + l2Proxy.forwardReward(); + + assertEq(l2Token.balanceOf(address(l2Proxy)), 0); + assertEq(l2Token.balanceOf(address(farm)), amount); + assertEq(farm.rewardRate(), amount / farm.rewardsDuration()); + } +} \ No newline at end of file From 2a65e8cfea02a2d464d046ff2a270ff5b40c5de8 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 24 May 2024 16:54:47 +0300 Subject: [PATCH 04/57] Update new-bridge --- lib/new-bridge | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new-bridge b/lib/new-bridge index 7b592a4..90817a8 160000 --- a/lib/new-bridge +++ b/lib/new-bridge @@ -1 +1 @@ -Subproject commit 7b592a4e1fb7dbd1e0c6a0fa4ef801415859abe1 +Subproject commit 90817a8261f4f3098c0296491e0d103bf60d28b0 From 79246a068aad74048d9ab51c3952674117727224 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 31 May 2024 11:48:31 +0300 Subject: [PATCH 05/57] Add default maxGas estimate --- src/L1FarmProxy.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index b898ece..ab0831b 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -39,7 +39,14 @@ interface InboxLike { contract L1FarmProxy { mapping (address => uint256) public wards; - uint64 public maxGas = 300_000; // TODO: figure out reasonable default for arbitrum-one + + // maxGas is estimated based on the formula in https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#breaking-down-the-formula + // where we use: + // * L2G = 44171 + // * (L1P)_max = 16 * 100 gwei + // * L1S = 367 + // * (L2P)_min = 0.01 gwei + uint64 public maxGas = 60_000_000; // > 44171 + (16 * 100 gwei * 367) / 0.01 gwei = 58_764_171; uint192 public gasPriceBid = 0.1 gwei; // 0.01 gwei arbitrum-one gas price floor * 10x factor address public immutable rewardsToken; @@ -92,7 +99,7 @@ contract L1FarmProxy { // @notice As this function is permissionless, it could in theory be called at a time where // maxGas and/or gasPriceBid are too low for the auto-redeem of the gem deposit RetryableTicket. // This is mitigated by incorporating large enough safety factors in maxGas and gasPriceBid. - // Note that in any case a failed auto-redeem can be permissonlessly retried for 7 days + // Note that in any case a failed auto-redeem can be permissionlessly retried for 7 days function notifyRewardAmount(uint256 reward) external { require(reward > 0, "L1FarmProxy/no-reward"); // prevent wasting gas for no-op From 45669bebacbf90b6284d23aaa3785cd55c254d34 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 31 May 2024 13:05:04 +0300 Subject: [PATCH 06/57] Add unit tests --- test/L1FarmProxy.t.sol | 105 +++++++++++++++++++++++++++++ test/L2FarmProxy.t.sol | 74 +++++++++++++++++++++ test/mocks/FarmMock.sol | 31 +++++++++ test/mocks/GemMock.sol | 106 ++++++++++++++++++++++++++++++ test/mocks/InboxMock.sol | 14 ++++ test/mocks/L1TokenGatewayMock.sol | 45 +++++++++++++ 6 files changed, 375 insertions(+) create mode 100644 test/L1FarmProxy.t.sol create mode 100644 test/L2FarmProxy.t.sol create mode 100644 test/mocks/FarmMock.sol create mode 100644 test/mocks/GemMock.sol create mode 100644 test/mocks/InboxMock.sol create mode 100644 test/mocks/L1TokenGatewayMock.sol diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol new file mode 100644 index 0000000..f9a2487 --- /dev/null +++ b/test/L1FarmProxy.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; + +import { L1FarmProxy } from "src/L1FarmProxy.sol"; +import { InboxMock } from "test/mocks/InboxMock.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; +import { L1TokenGatewayMock } from "test/mocks/L1TokenGatewayMock.sol"; + +contract L1FarmProxyTest is DssTest { + + GemMock rewardsToken; + L1FarmProxy l1Proxy; + address inbox; + address gateway; + address escrow = address(0xeee); + address l2Proxy = address(0x222); + address feeRecipient = address(0xfee); + + function setUp() public { + inbox = address(new InboxMock()); + gateway = address(new L1TokenGatewayMock(escrow)); + rewardsToken = new GemMock(1_000_000 ether); + l1Proxy = new L1FarmProxy(address(rewardsToken), l2Proxy, feeRecipient, inbox, gateway); + } + + function testConstructor() public { + vm.expectEmit(true, true, true, true); + emit Rely(address(this)); + L1FarmProxy g = new L1FarmProxy(address(rewardsToken), l2Proxy, feeRecipient, inbox, gateway); + + assertEq(g.rewardsToken(), address(rewardsToken)); + assertEq(g.l2Proxy(), l2Proxy); + assertEq(g.feeRecipient(), feeRecipient); + assertEq(address(g.inbox()), inbox); + assertEq(address(g.l1Gateway()), gateway); + assertEq(rewardsToken.allowance(address(g), gateway), type(uint256).max); + assertEq(g.wards(address(this)), 1); + } + + function testAuth() public { + checkAuth(address(l1Proxy), "L1FarmProxy"); + } + + function testFile() public { + checkFileUint(address(l1Proxy), "L1FarmProxy", ["maxGas", "gasPriceBid"]); + } + + function testAuthModifiers() public virtual { + l1Proxy.deny(address(this)); + + checkModifier(address(l1Proxy), string(abi.encodePacked("L1FarmProxy", "/not-authorized")), [ + l1Proxy.reclaim.selector + ]); + } + + function testReclaim() public { + (bool success,) = address(l1Proxy).call{value: 1 ether}(""); // not using deal() here, so as to check payable fallback + assertTrue(success); + address to = address(0x123); + uint256 proxyBefore = address(l1Proxy).balance; + uint256 toBefore = to.balance; + + l1Proxy.reclaim(to, 0.2 ether); + + assertEq(to.balance, toBefore + 0.2 ether); + assertEq(address(l1Proxy).balance, proxyBefore - 0.2 ether); + + vm.expectRevert("L1FarmProxy/failed-to-send-ether"); + l1Proxy.reclaim(address(this), 0.2 ether); // no fallback + } + + function testNotifyRewardAmount() public { + vm.expectRevert("L1FarmProxy/no-reward"); + l1Proxy.notifyRewardAmount(0); + + (bool success,) = address(l1Proxy).call{value: 1 ether}(""); + assertTrue(success); + uint256 proxyBefore = address(l1Proxy).balance; + assertEq(rewardsToken.balanceOf(escrow), 0); + + rewardsToken.transfer(address(l1Proxy), 1000 ether); + l1Proxy.notifyRewardAmount(1000 ether); + + assertEq(rewardsToken.balanceOf(escrow), 1000 ether); + assertLt(address(l1Proxy).balance, proxyBefore); + } +} diff --git a/test/L2FarmProxy.t.sol b/test/L2FarmProxy.t.sol new file mode 100644 index 0000000..a5208b9 --- /dev/null +++ b/test/L2FarmProxy.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; + +import { L2FarmProxy } from "src/L2FarmProxy.sol"; +import { FarmMock } from "test/mocks/FarmMock.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; + +contract L2FarmProxyTest is DssTest { + + GemMock rewardsToken; + L2FarmProxy l2Proxy; + address farm; + + event RewardAdded(uint256 rewards); + + function setUp() public { + rewardsToken = new GemMock(1_000_000 ether); + farm = address(new FarmMock(address(rewardsToken))); + l2Proxy = new L2FarmProxy(farm); + } + + function testConstructor() public { + vm.expectEmit(true, true, true, true); + emit Rely(address(this)); + L2FarmProxy g = new L2FarmProxy(farm); + assertEq(address(g.farm()), farm); + assertEq(address(g.rewardsToken()), address(rewardsToken)); + assertEq(g.wards(address(this)), 1); + } + + function testAuth() public { + checkAuth(address(l2Proxy), "L2FarmProxy"); + } + + function testFile() public { + checkFileUint(address(l2Proxy), "L2FarmProxy", ["minReward"]); + } + + function testForwardReward() public { + l2Proxy.file("minReward", 1000 ether); + + vm.expectRevert("L2FarmProxy/reward-too-small"); + l2Proxy.forwardReward(); + + rewardsToken.transfer(address(l2Proxy), 10_000 ether); + assertEq(rewardsToken.balanceOf(farm), 0); + assertEq(rewardsToken.balanceOf(address(l2Proxy)), 10_000 ether); + + vm.expectEmit(true, true, true, true); + emit RewardAdded(10_000 ether); + l2Proxy.forwardReward(); + + assertEq(rewardsToken.balanceOf(farm), 10_000 ether); + assertEq(rewardsToken.balanceOf(address(l2Proxy)), 0); + } +} diff --git a/test/mocks/FarmMock.sol b/test/mocks/FarmMock.sol new file mode 100644 index 0000000..ce8eec3 --- /dev/null +++ b/test/mocks/FarmMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +contract FarmMock { + address public immutable rewardsToken; + event RewardAdded(uint256 rewards); + + constructor(address _rewardsToken) { + rewardsToken = _rewardsToken; + } + + function notifyRewardAmount(uint256 reward) external { + emit RewardAdded(reward); + } +} diff --git a/test/mocks/GemMock.sol b/test/mocks/GemMock.sol new file mode 100644 index 0000000..f5d2ed0 --- /dev/null +++ b/test/mocks/GemMock.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +contract GemMock { + mapping (address => uint256) public wards; + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + uint256 public totalSupply; + + constructor(uint256 initialSupply) { + wards[msg.sender] = 1; + + mint(msg.sender, initialSupply); + } + + modifier auth() { + require(wards[msg.sender] == 1, "Gem/not-authorized"); + _; + } + + function rely(address usr) external auth { wards[usr] = 1; } + function deny(address usr) external auth { wards[usr] = 0; } + + function approve(address spender, uint256 value) external returns (bool) { + allowance[msg.sender][spender] = value; + return true; + } + + function transfer(address to, uint256 value) external returns (bool) { + uint256 balance = balanceOf[msg.sender]; + require(balance >= value, "Gem/insufficient-balance"); + + unchecked { + balanceOf[msg.sender] = balance - value; + balanceOf[to] += value; + } + return true; + } + + function transferFrom(address from, address to, uint256 value) external returns (bool) { + uint256 balance = balanceOf[from]; + require(balance >= value, "Gem/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "Gem/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + balanceOf[to] += value; + } + return true; + } + + function mint(address to, uint256 value) public auth { + unchecked { + balanceOf[to] = balanceOf[to] + value; + } + totalSupply = totalSupply + value; + } + + function burn(address from, uint256 value) external { + uint256 balance = balanceOf[from]; + require(balance >= value, "Gem/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "Gem/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + totalSupply = totalSupply - value; + } + } +} diff --git a/test/mocks/InboxMock.sol b/test/mocks/InboxMock.sol new file mode 100644 index 0000000..267c346 --- /dev/null +++ b/test/mocks/InboxMock.sol @@ -0,0 +1,14 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.21; + +contract InboxMock { + function calculateRetryableSubmissionFee( + uint256 dataLength, + uint256 baseFee + ) external view returns (uint256 fee) { + fee = (1400 + 6 * dataLength) * (baseFee == 0 ? block.basefee : baseFee); + } +} diff --git a/test/mocks/L1TokenGatewayMock.sol b/test/mocks/L1TokenGatewayMock.sol new file mode 100644 index 0000000..fa2b9d4 --- /dev/null +++ b/test/mocks/L1TokenGatewayMock.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +interface TokenLike { + function transferFrom(address, address, uint256) external; +} + +contract L1TokenGatewayMock { + address public immutable escrow; + + constructor( + address _escrow + ) { + escrow = _escrow; + } + + function outboundTransferCustomRefund( + address l1Token, + address /* refundTo */, + address /* to */, + uint256 amount, + uint256 /* maxGas */, + uint256 /* gasPriceBid */, + bytes calldata /* data */ + ) public payable returns (bytes memory res) { + TokenLike(l1Token).transferFrom(msg.sender, escrow, amount); + res = abi.encode(0); + } +} From 4ef9be8b8b5ec952ed74eb6acd112924a3b01202 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 31 May 2024 14:54:37 +0300 Subject: [PATCH 07/57] Add vest setup to init script --- deploy/FarmProxyInit.sol | 34 ++++++++++++++++-- test/Integration.t.sol | 76 +++++++++++++++++++--------------------- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index c8aca32..7d9723d 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -20,6 +20,15 @@ import { DssInstance } from "dss-test/MCD.sol"; import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; +interface DssVestLike { + function create(address _usr, uint256 _tot, uint256 _bgn, uint256 _tau, uint256 _eta, address _mgr) external returns (uint256 id); + function restrict(uint256 _id) external; +} + +interface VestedRewardsDistributionLike { + function file(bytes32 what, uint256 data) external; +} + interface L1FarmProxyLike { function rewardsToken() external view returns (address); function l2Proxy() external view returns (address); @@ -46,6 +55,12 @@ struct MessageParams { } struct ProxiesConfig { + address vest; + address vestedRewardDistribution; + uint256 vestTot; + uint256 vestBgn; + uint256 vestTau; + address vestMgr; address rewardsToken; address l2Proxy; address feeRecipient; @@ -66,14 +81,30 @@ library FarmProxyInit { L1FarmProxyLike l1Proxy = L1FarmProxyLike(l1Proxy_); // sanity checks + require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/gem-mismatch"); require(l1Proxy.l2Proxy() == cfg.l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); - L1RelayLike l1GovRelay = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")); + // setup vest + DssVestLike vest = DssVestLike(cfg.vest); + uint256 vestId = vest.create({ + _usr: cfg.vestedRewardDistribution, + _tot: cfg.vestTot, + _bgn: cfg.vestBgn, + _tau: cfg.vestTau, + _eta: 0, + _mgr: cfg.vestMgr + }); + vest.restrict(vestId); + VestedRewardsDistributionLike(cfg.vestedRewardDistribution).file("vestId", vestId); + + // relay L2 spell + + L1RelayLike l1GovRelay = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")); uint256 l1CallValue = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; // not strictly necessary (as the retryable ticket creation would otherwise fail) @@ -88,7 +119,6 @@ library FarmProxyInit { gasPriceBid: cfg.xchainMsg.gasPriceBid, maxSubmissionCost: cfg.xchainMsg.maxSubmissionCost }); - dss.chainlog.setAddress("ARBITRUM_L1_FARM_PROXY", l1Proxy_); } } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index fdc8bf5..305cbf2 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -30,7 +30,6 @@ import { GemMock } from "lib/new-bridge/test/mocks/GemMock.sol"; import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; -import { VestedRewardsDistributionInit, VestedRewardsDistributionInitParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionInit.sol"; import { VestedRewardsDistribution } from "lib/endgame-toolkit/src/VestedRewardsDistribution.sol"; import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; @@ -49,12 +48,14 @@ interface GemLike { } interface DssVestLike { + function usr(uint256) external view returns (address); function bgn(uint256) external view returns (uint256); + function clf(uint256) external view returns (uint256); function fin(uint256) external view returns (uint256); + function mgr(uint256) external view returns (address); + function res(uint256) external view returns (uint256); function tot(uint256) external view returns (uint256); function unpaid(uint256 _id) external view returns (uint256); - function create(address _usr, uint256 _tot, uint256 _bgn, uint256 _tau, uint256 _eta, address _mgr) external returns (uint256 id); - function restrict(uint256 _id) external; } contract IntegrationTest is DssTest { @@ -184,23 +185,38 @@ contract IntegrationTest is DssTest { inbox: INBOX, l1Gateway: l1Gateway }); - (bool success,) = l1Proxy.call{value: 1 ether}(""); // not using deal() here, so as to check payable fallback - assertTrue(success); + vest = DssVestLike(dss.chainlog.getAddress("MCD_VEST_MKR")); // TODO: change to use NGT vest when deployed + VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ + deployer: address(this), + owner: PAUSE_PROXY, + vest: address(vest), + rewards: l1Proxy + }); + vestedRewardDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams)); + + (bool success,) = l1Proxy.call{value: 1 ether}(""); + assertTrue(success); ProxyMessageParams memory xchainMsg = ProxyMessageParams({ gasPriceBid: 0.1 gwei, maxGas: 300_000, maxSubmissionCost: 0.01 ether }); ProxiesConfig memory cfg = ProxiesConfig({ - rewardsToken: l1Token, - l2Proxy: address(l2Proxy), - feeRecipient: L2_GOV_RELAY, - inbox: INBOX, - l1Gateway: l1Gateway, - minReward: 1 ether, - rewardsDuration: 1 days, - xchainMsg: xchainMsg + vest: address(vest), + vestedRewardDistribution: address(vestedRewardDistribution), + vestTot: 100 * 1e18, + vestBgn: block.timestamp, + vestTau: 100 days, + vestMgr: address(0), + rewardsToken: l1Token, + l2Proxy: address(l2Proxy), + feeRecipient: L2_GOV_RELAY, + inbox: INBOX, + l1Gateway: l1Gateway, + minReward: 1 ether, + rewardsDuration: 1 days, + xchainMsg: xchainMsg }); vm.startPrank(PAUSE_PROXY); FarmProxyInit.initProxies(dss, l1Proxy, l2ProxyInstance, cfg); @@ -208,6 +224,14 @@ contract IntegrationTest is DssTest { // test L1 side of initProxies assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), l1Proxy); + vestId = vestedRewardDistribution.vestId(); + assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); + assertEq(vest.tot(vestId), cfg.vestTot); + assertEq(vest.bgn(vestId), cfg.vestBgn); + assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); + assertEq(vest.clf(vestId), cfg.vestBgn); + assertEq(vest.mgr(vestId), cfg.vestMgr); + assertEq(vest.res(vestId), 1); l2Domain.relayFromHost(true); @@ -215,32 +239,6 @@ contract IntegrationTest is DssTest { assertEq(l2Proxy.minReward(), cfg.minReward); assertEq(farm.rewardsDistribution(), address(l2Proxy)); assertEq(farm.rewardsDuration(), cfg.rewardsDuration); - - l1Domain.selectFork(); - vest = DssVestLike(dss.chainlog.getAddress("MCD_VEST_MKR")); // TODO: change to use NGT vest when deployed - VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ - deployer: address(this), - owner: PAUSE_PROXY, - vest: address(vest), - rewards: l1Proxy - }); - vestedRewardDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams)); - - vm.startPrank(PAUSE_PROXY); - vestId = vest.create({ - _usr: address(vestedRewardDistribution), - _tot: 100 * 1e18, - _bgn: block.timestamp, - _tau: 100 days, - _eta: 0, - _mgr: address(0) - }); - vest.restrict(vestId); - VestedRewardsDistributionInitParams memory distributionInitParams = VestedRewardsDistributionInitParams({ - vestId: vestId - }); - VestedRewardsDistributionInit.init(address(vestedRewardDistribution), distributionInitParams); - vm.stopPrank(); } function testDistribution() public { From 5f4203480b4d081efd188746d4af61790447dd3a Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 5 Jun 2024 20:50:58 +0300 Subject: [PATCH 08/57] Add maxGas estimation script --- script/Estimate.s.sol | 95 ++++++++++++++++++++++++++++++++++++++ script/input/1/config.json | 8 +++- src/L1FarmProxy.sol | 8 +--- 3 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 script/Estimate.s.sol diff --git a/script/Estimate.s.sol b/script/Estimate.s.sol new file mode 100644 index 0000000..7e6fc52 --- /dev/null +++ b/script/Estimate.s.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; + +interface GatewayLike { + function getOutboundCalldata( + address l1Token, + address from, + address to, + uint256 amount, + bytes memory data + ) external pure returns (bytes memory); +} + +// Estimate `maxGas` for L1FarmProxy +contract Estimate is Script { + using stdJson for string; + + function run() external { + string memory config = ScriptTools.readInput("config"); // loads from FOUNDRY_SCRIPT_CONFIG + + Domain l1Domain = new Domain(config, getChain(string(vm.envOr("L1", string("mainnet"))))); + Domain l2Domain = new Domain(config, getChain(vm.envOr("L2", string("arbitrum_one")))); + l1Domain.selectFork(); + + (, address deployer,) = vm.readCallers(); + address l1Gateway = l1Domain.readConfigAddress("gateway"); + address l1Nst = l1Domain.readConfigAddress("nst"); + address l2Gateway = l2Domain.readConfigAddress("gateway"); + + bytes memory finalizeDepositCalldata = GatewayLike(l1Gateway).getOutboundCalldata({ + l1Token: l1Nst, + from: deployer, + to: address(uint160(uint256(keccak256(abi.encode(deployer, block.timestamp))))), // a pseudo-random address used as "fresh" destination address, + amount: uint128(uint256(keccak256(abi.encode(deployer)))), // very large random-looking number => costlier calldata + data: "" + }); + bytes memory data = abi.encodeWithSignature( + "gasEstimateComponents(address,bool,bytes)", + l2Gateway, + false, + finalizeDepositCalldata + ); + address l2Sender = address(uint160(l1Gateway) + uint160(0x1111000000000000000000000000000000001111)); + + l2Domain.selectFork(); + bytes memory res = vm.rpc("eth_call", string(abi.encodePacked( + "[{\"to\": \"", + vm.toString(address(0xc8)), // NodeInterface + "\", \"from\": \"", + vm.toString(l2Sender), + "\", \"data\": \"", + vm.toString(data), + "\"}]" + ))); + + (uint64 gasEstimate, uint64 gasEstimateForL1, uint256 l2BaseFee, uint256 l1BaseFeeEstimate) + = abi.decode(res, (uint64,uint64,uint256,uint256)); + + + uint256 l2g = gasEstimate - gasEstimateForL1; + uint256 l1p = 16 * l1BaseFeeEstimate; + uint256 l1s = gasEstimateForL1 * l2BaseFee / l1p; + + // maxGas is estimated based on the formula in https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#breaking-down-the-formula + // where we use: + // * (L1P)_max = 16 * 100 gwei + // * (P)_min = 0.01 gwei + uint256 maxGas = l2g + (16 * 100 gwei * l1s) / 0.01 gwei; + + console2.log("L2G:", l2g); + console2.log("L1S:", l1s); + console2.log("Recommended maxGas:", maxGas); + } +} \ No newline at end of file diff --git a/script/input/1/config.json b/script/input/1/config.json index 56187f5..b8b2336 100644 --- a/script/input/1/config.json +++ b/script/input/1/config.json @@ -13,13 +13,17 @@ }, "sepolia": { "type": "root", - "rpc": "SEPOLIA_RPC_URL" + "rpc": "SEPOLIA_RPC_URL", + "gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", + "nst": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84" }, "arbitrum_one_sepolia": { "type": "arbitrum", "rpc": "ARBITRUM_ONE_SEPOLIA_RPC_URL", "inbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", - "arbSys": "0x0000000000000000000000000000000000000064" + "arbSys": "0x0000000000000000000000000000000000000064", + "gateway": "0x0CB73f1A5732fbAeFAB5646B422eCF15E12db95B", + "nst": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c" } } } diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index ab0831b..663bd97 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -40,13 +40,7 @@ interface InboxLike { contract L1FarmProxy { mapping (address => uint256) public wards; - // maxGas is estimated based on the formula in https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#breaking-down-the-formula - // where we use: - // * L2G = 44171 - // * (L1P)_max = 16 * 100 gwei - // * L1S = 367 - // * (L2P)_min = 0.01 gwei - uint64 public maxGas = 60_000_000; // > 44171 + (16 * 100 gwei * 367) / 0.01 gwei = 58_764_171; + uint64 public maxGas = 70_000_000; // determined by running deploy/Estimate.s.sol and adding some margin uint192 public gasPriceBid = 0.1 gwei; // 0.01 gwei arbitrum-one gas price floor * 10x factor address public immutable rewardsToken; From 4cede7b4303c7f6dba399b99427e3284a6159d3b Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 13 Jun 2024 20:10:16 +0300 Subject: [PATCH 09/57] Add minReward to L1Proxy --- deploy/FarmProxyInit.sol | 25 +++++++++++++++++++----- src/L1FarmProxy.sol | 41 +++++++++++++++++++++------------------- test/Integration.t.sol | 22 ++++++++++++--------- test/L1FarmProxy.t.sol | 25 ++++++++++++++++-------- test/mocks/InboxMock.sol | 2 +- 5 files changed, 73 insertions(+), 42 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 7d9723d..7293db9 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -35,6 +35,7 @@ interface L1FarmProxyLike { function feeRecipient() external view returns (address); function inbox() external view returns (address); function l1Gateway() external view returns (address); + function file(bytes32 what, uint256 data) external; } interface L1RelayLike { @@ -66,9 +67,12 @@ struct ProxiesConfig { address feeRecipient; address inbox; address l1Gateway; - uint256 minReward; - uint256 rewardsDuration; - MessageParams xchainMsg; + uint256 maxGas; // For the L1 proxy + uint256 gasPriceBid; // For the L1 proxy + uint256 l1MinReward; // For the L1 proxy + uint256 l2MinReward; // For the L2 proxy + uint256 rewardsDuration; // For the farm on L2 + MessageParams xchainMsg; // For the xchain message executing the L2 spell } library FarmProxyInit { @@ -87,6 +91,10 @@ library FarmProxyInit { require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); + require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); + require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); + require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); + require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); // setup vest @@ -102,7 +110,11 @@ library FarmProxyInit { vest.restrict(vestId); VestedRewardsDistributionLike(cfg.vestedRewardDistribution).file("vestId", vestId); - // relay L2 spell + // setup L1 proxy + + l1Proxy.file("minReward", cfg.l1MinReward); + + // setup L2 proxy L1RelayLike l1GovRelay = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")); uint256 l1CallValue = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; @@ -113,12 +125,15 @@ library FarmProxyInit { l1GovRelay.relay({ target: l2ProxyInstance.spell, - targetData: abi.encodeCall(L2FarmProxySpell.init, (cfg.minReward, cfg.rewardsDuration)), + targetData: abi.encodeCall(L2FarmProxySpell.init, (cfg.l2MinReward, cfg.rewardsDuration)), l1CallValue: l1CallValue, maxGas: cfg.xchainMsg.maxGas, gasPriceBid: cfg.xchainMsg.gasPriceBid, maxSubmissionCost: cfg.xchainMsg.maxSubmissionCost }); + + // update chainlog + dss.chainlog.setAddress("ARBITRUM_L1_FARM_PROXY", l1Proxy_); } } diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 663bd97..e25a1c3 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -39,9 +39,9 @@ interface InboxLike { contract L1FarmProxy { mapping (address => uint256) public wards; - - uint64 public maxGas = 70_000_000; // determined by running deploy/Estimate.s.sol and adding some margin - uint192 public gasPriceBid = 0.1 gwei; // 0.01 gwei arbitrum-one gas price floor * 10x factor + uint64 public maxGas; + uint64 public gasPriceBid; + uint128 public minReward; address public immutable rewardsToken; address public immutable l2Proxy; @@ -74,9 +74,12 @@ contract L1FarmProxy { function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } + // @notice Validation of the `data` boundaries is outside the scope of this + // contract and is assumed to be carried out in the corresponding spell process function file(bytes32 what, uint256 data) external auth { if (what == "maxGas") maxGas = uint64(data); - else if (what == "gasPriceBid") gasPriceBid = uint192(data); + else if (what == "gasPriceBid") gasPriceBid = uint64(data); + else if (what == "minReward") minReward = uint128(data); else revert("L1FarmProxy/file-unrecognized-param"); emit File(what, data); } @@ -95,20 +98,20 @@ contract L1FarmProxy { // This is mitigated by incorporating large enough safety factors in maxGas and gasPriceBid. // Note that in any case a failed auto-redeem can be permissionlessly retried for 7 days function notifyRewardAmount(uint256 reward) external { - require(reward > 0, "L1FarmProxy/no-reward"); // prevent wasting gas for no-op - - (uint256 maxGas_, uint256 gasPriceBid_) = (maxGas, gasPriceBid); - uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes - uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; - - l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ - l1Token: rewardsToken, - refundTo: feeRecipient, - to: l2Proxy, - amount: reward, - maxGas: maxGas_, - gasPriceBid: gasPriceBid_, - data: abi.encode(maxSubmissionCost, bytes("")) - }); + (uint256 maxGas_, uint256 gasPriceBid_, uint256 minReward_) = (maxGas, gasPriceBid, minReward); + if (reward > 0 && reward >= minReward_) { + uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes + uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; + + l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ + l1Token: rewardsToken, + refundTo: feeRecipient, + to: l2Proxy, + amount: reward, + maxGas: maxGas_, + gasPriceBid: gasPriceBid_, + data: abi.encode(maxSubmissionCost, bytes("")) + }); + } } } \ No newline at end of file diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 305cbf2..92ad35a 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -70,7 +70,7 @@ contract IntegrationTest is DssTest { address l1Token; address l1Gateway; address INBOX; - address l1Proxy; + L1FarmProxy l1Proxy; DssVestLike vest; uint256 vestId; VestedRewardsDistribution vestedRewardDistribution; @@ -176,7 +176,7 @@ contract IntegrationTest is DssTest { assertEq(address(L2FarmProxySpell(l2ProxyInstance.spell).l2Proxy()), address(l2Proxy)); l1Domain.selectFork(); - l1Proxy = FarmProxyDeploy.deployL1Proxy({ + l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ deployer: address(this), owner: PAUSE_PROXY, gem: l1Token, @@ -184,18 +184,18 @@ contract IntegrationTest is DssTest { feeRecipient: L2_GOV_RELAY, inbox: INBOX, l1Gateway: l1Gateway - }); + }))); vest = DssVestLike(dss.chainlog.getAddress("MCD_VEST_MKR")); // TODO: change to use NGT vest when deployed VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ deployer: address(this), owner: PAUSE_PROXY, vest: address(vest), - rewards: l1Proxy + rewards: address(l1Proxy) }); vestedRewardDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams)); - (bool success,) = l1Proxy.call{value: 1 ether}(""); + (bool success,) = address(l1Proxy).call{value: 1 ether}(""); assertTrue(success); ProxyMessageParams memory xchainMsg = ProxyMessageParams({ gasPriceBid: 0.1 gwei, @@ -214,16 +214,19 @@ contract IntegrationTest is DssTest { feeRecipient: L2_GOV_RELAY, inbox: INBOX, l1Gateway: l1Gateway, - minReward: 1 ether, + maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin + gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor + l1MinReward: 0, + l2MinReward: 1 ether, rewardsDuration: 1 days, xchainMsg: xchainMsg }); vm.startPrank(PAUSE_PROXY); - FarmProxyInit.initProxies(dss, l1Proxy, l2ProxyInstance, cfg); + FarmProxyInit.initProxies(dss, address(l1Proxy), l2ProxyInstance, cfg); vm.stopPrank(); // test L1 side of initProxies - assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), l1Proxy); + assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), address(l1Proxy)); vestId = vestedRewardDistribution.vestId(); assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); assertEq(vest.tot(vestId), cfg.vestTot); @@ -232,11 +235,12 @@ contract IntegrationTest is DssTest { assertEq(vest.clf(vestId), cfg.vestBgn); assertEq(vest.mgr(vestId), cfg.vestMgr); assertEq(vest.res(vestId), 1); + assertEq(l1Proxy.minReward(), cfg.l1MinReward); l2Domain.relayFromHost(true); // test L2 side of initProxies - assertEq(l2Proxy.minReward(), cfg.minReward); + assertEq(l2Proxy.minReward(), cfg.l2MinReward); assertEq(farm.rewardsDistribution(), address(l2Proxy)); assertEq(farm.rewardsDuration(), cfg.rewardsDuration); } diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index f9a2487..4f1f03a 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -60,7 +60,7 @@ contract L1FarmProxyTest is DssTest { } function testFile() public { - checkFileUint(address(l1Proxy), "L1FarmProxy", ["maxGas", "gasPriceBid"]); + checkFileUint(address(l1Proxy), "L1FarmProxy", ["maxGas", "gasPriceBid", "minReward"]); } function testAuthModifiers() public virtual { @@ -88,18 +88,27 @@ contract L1FarmProxyTest is DssTest { } function testNotifyRewardAmount() public { - vm.expectRevert("L1FarmProxy/no-reward"); - l1Proxy.notifyRewardAmount(0); - (bool success,) = address(l1Proxy).call{value: 1 ether}(""); assertTrue(success); - uint256 proxyBefore = address(l1Proxy).balance; - assertEq(rewardsToken.balanceOf(escrow), 0); - rewardsToken.transfer(address(l1Proxy), 1000 ether); + uint256 ethBefore = address(l1Proxy).balance; + uint256 tokenBefore = rewardsToken.balanceOf(address(l1Proxy)); + + l1Proxy.notifyRewardAmount(0); + + assertEq(address(l1Proxy).balance, ethBefore); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), tokenBefore); + + l1Proxy.file("minReward", 1000 ether); + l1Proxy.notifyRewardAmount(500 ether); + + assertEq(address(l1Proxy).balance, ethBefore); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), tokenBefore); + l1Proxy.notifyRewardAmount(1000 ether); assertEq(rewardsToken.balanceOf(escrow), 1000 ether); - assertLt(address(l1Proxy).balance, proxyBefore); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0); + assertLt(address(l1Proxy).balance, ethBefore); } } diff --git a/test/mocks/InboxMock.sol b/test/mocks/InboxMock.sol index 267c346..71d2450 100644 --- a/test/mocks/InboxMock.sol +++ b/test/mocks/InboxMock.sol @@ -9,6 +9,6 @@ contract InboxMock { uint256 dataLength, uint256 baseFee ) external view returns (uint256 fee) { - fee = (1400 + 6 * dataLength) * (baseFee == 0 ? block.basefee : baseFee); + fee = (1400 + 6 * dataLength) * (baseFee == 0 ? 30 gwei : baseFee); } } From f88061e3eb7409f9d0710a16ec7bdddc2f0d97c7 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 14 Jun 2024 16:18:27 +0300 Subject: [PATCH 10/57] Revert when reward is too small on L1 --- src/L1FarmProxy.sol | 29 +++++++++++++++-------------- test/L1FarmProxy.t.sol | 18 +++++++----------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index e25a1c3..9a7ef76 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -99,19 +99,20 @@ contract L1FarmProxy { // Note that in any case a failed auto-redeem can be permissionlessly retried for 7 days function notifyRewardAmount(uint256 reward) external { (uint256 maxGas_, uint256 gasPriceBid_, uint256 minReward_) = (maxGas, gasPriceBid, minReward); - if (reward > 0 && reward >= minReward_) { - uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes - uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; - - l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ - l1Token: rewardsToken, - refundTo: feeRecipient, - to: l2Proxy, - amount: reward, - maxGas: maxGas_, - gasPriceBid: gasPriceBid_, - data: abi.encode(maxSubmissionCost, bytes("")) - }); - } + + require(reward > 0 && reward >= minReward_, "L1FarmProxy/reward-too-small"); + + uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes + uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; + + l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ + l1Token: rewardsToken, + refundTo: feeRecipient, + to: l2Proxy, + amount: reward, + maxGas: maxGas_, + gasPriceBid: gasPriceBid_, + data: abi.encode(maxSubmissionCost, bytes("")) + }); } } \ No newline at end of file diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index 4f1f03a..c2ebf08 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -88,22 +88,18 @@ contract L1FarmProxyTest is DssTest { } function testNotifyRewardAmount() public { - (bool success,) = address(l1Proxy).call{value: 1 ether}(""); - assertTrue(success); - rewardsToken.transfer(address(l1Proxy), 1000 ether); - uint256 ethBefore = address(l1Proxy).balance; - uint256 tokenBefore = rewardsToken.balanceOf(address(l1Proxy)); - + vm.expectRevert("L1FarmProxy/reward-too-small"); l1Proxy.notifyRewardAmount(0); - assertEq(address(l1Proxy).balance, ethBefore); - assertEq(rewardsToken.balanceOf(address(l1Proxy)), tokenBefore); - l1Proxy.file("minReward", 1000 ether); + + vm.expectRevert("L1FarmProxy/reward-too-small"); l1Proxy.notifyRewardAmount(500 ether); - assertEq(address(l1Proxy).balance, ethBefore); - assertEq(rewardsToken.balanceOf(address(l1Proxy)), tokenBefore); + (bool success,) = address(l1Proxy).call{value: 1 ether}(""); + assertTrue(success); + rewardsToken.transfer(address(l1Proxy), 1000 ether); + uint256 ethBefore = address(l1Proxy).balance; l1Proxy.notifyRewardAmount(1000 ether); From d98c65a85628b49261d0857aa2bdf143c1f806af Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 14 Jun 2024 17:07:49 +0300 Subject: [PATCH 11/57] Add estimateDepositCost view func --- src/L1FarmProxy.sol | 19 ++++++++++++++++--- test/L1FarmProxy.t.sol | 3 ++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 9a7ef76..2157f92 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -93,6 +93,21 @@ contract L1FarmProxy { require(sent, "L1FarmProxy/failed-to-send-ether"); } + // @notice Estimate the amount of ETH consumed as msg.value from this contract to bridge the reward to the L2 proxy + // as well as the RetryableTicket submission cost. + // @param l1BaseFee L1 baseFee to use for the estimate. Pass 0 to use block.basefee + // @param _maxGas Max gas to cover the L2 execution of the deposit. Pass 0 to use the stored `maxGas` value. + // @param _gasPriceBid Gas price bid for the L2 execution of the deposit. Pass 0 to use the stored `gasPriceBid` value. + function estimateDepositCost( + uint256 l1BaseFee, + uint256 _maxGas, + uint256 _gasPriceBid + ) public view returns (uint256 l1CallValue, uint256 maxSubmissionCost) { + maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, l1BaseFee); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes + (uint256 maxGas_, uint256 gasPriceBid_) = (_maxGas > 0 ? _maxGas : maxGas, _gasPriceBid > 0 ? _gasPriceBid : gasPriceBid); + l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; + } + // @notice As this function is permissionless, it could in theory be called at a time where // maxGas and/or gasPriceBid are too low for the auto-redeem of the gem deposit RetryableTicket. // This is mitigated by incorporating large enough safety factors in maxGas and gasPriceBid. @@ -102,9 +117,7 @@ contract L1FarmProxy { require(reward > 0 && reward >= minReward_, "L1FarmProxy/reward-too-small"); - uint256 maxSubmissionCost = inbox.calculateRetryableSubmissionFee(324, 0); // size of finalizeInboundTransfer calldata = 4 + 10*32 bytes - uint256 l1CallValue = maxSubmissionCost + maxGas_ * gasPriceBid_; - + (uint256 l1CallValue, uint256 maxSubmissionCost) = estimateDepositCost(0, maxGas_, gasPriceBid_); l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ l1Token: rewardsToken, refundTo: feeRecipient, diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index c2ebf08..c97bc4a 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -100,11 +100,12 @@ contract L1FarmProxyTest is DssTest { assertTrue(success); rewardsToken.transfer(address(l1Proxy), 1000 ether); uint256 ethBefore = address(l1Proxy).balance; + (uint256 l1CallValue,) = l1Proxy.estimateDepositCost(0, 0, 0); l1Proxy.notifyRewardAmount(1000 ether); assertEq(rewardsToken.balanceOf(escrow), 1000 ether); assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0); - assertLt(address(l1Proxy).balance, ethBefore); + assertEq(address(l1Proxy).balance, ethBefore - l1CallValue); } } From e1c107b595f8505f01d35e83fac4069e53c087dd Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:11:56 +0300 Subject: [PATCH 12/57] Apply suggestions from code review Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/L1FarmProxy.sol | 2 +- test/Integration.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 2157f92..2fe0376 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -128,4 +128,4 @@ contract L1FarmProxy { data: abi.encode(maxSubmissionCost, bytes("")) }); } -} \ No newline at end of file +} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 92ad35a..13de45a 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -269,4 +269,4 @@ contract IntegrationTest is DssTest { assertEq(l2Token.balanceOf(address(farm)), amount); assertEq(farm.rewardRate(), amount / farm.rewardsDuration()); } -} \ No newline at end of file +} From b498cd96a6c59348be86511728765606c5d3c08c Mon Sep 17 00:00:00 2001 From: telome <> Date: Mon, 24 Jun 2024 12:32:58 +0300 Subject: [PATCH 13/57] Update arbitrum-token-bridge --- .gitmodules | 6 +++--- lib/new-bridge | 1 - test/Integration.t.sol | 10 +++++----- 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 160000 lib/new-bridge diff --git a/.gitmodules b/.gitmodules index f47e6d0..da3b3e9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "lib/dss-test"] path = lib/dss-test url = https://github.com/makerdao/dss-test -[submodule "lib/new-bridge"] - path = lib/new-bridge - url = https://github.com/telome/new-bridge +[submodule "lib/arbitrum-token-bridge"] + path = lib/arbitrum-token-bridge + url = https://github.com/makerdao/arbitrum-token-bridge [submodule "lib/endgame-toolkit"] path = lib/endgame-toolkit url = https://github.com/makerdao/endgame-toolkit diff --git a/lib/new-bridge b/lib/new-bridge deleted file mode 160000 index 90817a8..0000000 --- a/lib/new-bridge +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 90817a8261f4f3098c0296491e0d103bf60d28b0 diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 13de45a..be1f968 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -22,11 +22,11 @@ import "dss-test/DssTest.sol"; import { Domain } from "dss-test/domains/Domain.sol"; import { ArbitrumDomain } from "dss-test/domains/ArbitrumDomain.sol"; -import { TokenGatewayDeploy } from "lib/new-bridge/deploy/TokenGatewayDeploy.sol"; -import { L2TokenGatewaySpell } from "lib/new-bridge/deploy/L2TokenGatewaySpell.sol"; -import { L2TokenGatewayInstance } from "lib/new-bridge/deploy/L2TokenGatewayInstance.sol"; -import { TokenGatewayInit, GatewaysConfig, MessageParams as GatewayMessageParams } from "lib/new-bridge/deploy/TokenGatewayInit.sol"; -import { GemMock } from "lib/new-bridge/test/mocks/GemMock.sol"; +import { TokenGatewayDeploy } from "lib/arbitrum-token-bridge/deploy/TokenGatewayDeploy.sol"; +import { L2TokenGatewaySpell } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewaySpell.sol"; +import { L2TokenGatewayInstance } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewayInstance.sol"; +import { TokenGatewayInit, GatewaysConfig, MessageParams as GatewayMessageParams } from "lib/arbitrum-token-bridge/deploy/TokenGatewayInit.sol"; +import { GemMock } from "lib/arbitrum-token-bridge/test/mocks/GemMock.sol"; import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; From ad7048c5ec0eb9d16821c17ff6fe5121382580d1 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:35:47 +0300 Subject: [PATCH 14/57] Apply suggestions from code review Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- deploy/FarmProxyInit.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 7293db9..102182c 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -80,13 +80,13 @@ library FarmProxyInit { DssInstance memory dss, address l1Proxy_, L2FarmProxyInstance memory l2ProxyInstance, - ProxiesConfig memory cfg + ProxiesConfig memory cfg ) internal { L1FarmProxyLike l1Proxy = L1FarmProxyLike(l1Proxy_); // sanity checks - require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/gem-mismatch"); + require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/rewards-token-mismatch"); require(l1Proxy.l2Proxy() == cfg.l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); From c86e45edc1f1549187ce83578aa11da0a6c39f5f Mon Sep 17 00:00:00 2001 From: telome <> Date: Mon, 24 Jun 2024 12:41:41 +0300 Subject: [PATCH 15/57] File gas params in spell --- deploy/FarmProxyInit.sol | 6 ++++-- test/Integration.t.sol | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 102182c..3631af8 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -91,8 +91,8 @@ library FarmProxyInit { require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); - require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); + require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); @@ -112,7 +112,9 @@ library FarmProxyInit { // setup L1 proxy - l1Proxy.file("minReward", cfg.l1MinReward); + l1Proxy.file("maxGas", cfg.maxGas); + l1Proxy.file("gasPriceBid", cfg.gasPriceBid); + l1Proxy.file("minReward", cfg.l1MinReward); // setup L2 proxy diff --git a/test/Integration.t.sol b/test/Integration.t.sol index be1f968..91dbb55 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -228,21 +228,23 @@ contract IntegrationTest is DssTest { // test L1 side of initProxies assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), address(l1Proxy)); vestId = vestedRewardDistribution.vestId(); - assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); - assertEq(vest.tot(vestId), cfg.vestTot); - assertEq(vest.bgn(vestId), cfg.vestBgn); - assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); - assertEq(vest.clf(vestId), cfg.vestBgn); - assertEq(vest.mgr(vestId), cfg.vestMgr); - assertEq(vest.res(vestId), 1); - assertEq(l1Proxy.minReward(), cfg.l1MinReward); + assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); + assertEq(vest.tot(vestId), cfg.vestTot); + assertEq(vest.bgn(vestId), cfg.vestBgn); + assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); + assertEq(vest.clf(vestId), cfg.vestBgn); + assertEq(vest.mgr(vestId), cfg.vestMgr); + assertEq(vest.res(vestId), 1); + assertEq(l1Proxy.maxGas(), cfg.maxGas); + assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); + assertEq(l1Proxy.minReward(), cfg.l1MinReward); l2Domain.relayFromHost(true); // test L2 side of initProxies - assertEq(l2Proxy.minReward(), cfg.l2MinReward); + assertEq(l2Proxy.minReward(), cfg.l2MinReward); assertEq(farm.rewardsDistribution(), address(l2Proxy)); - assertEq(farm.rewardsDuration(), cfg.rewardsDuration); + assertEq(farm.rewardsDuration(), cfg.rewardsDuration); } function testDistribution() public { From c2eb5abce9708df073d0be666abece301f3c8fb7 Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 11:32:16 +0300 Subject: [PATCH 16/57] Update arbitrum-token-bridge --- lib/arbitrum-token-bridge | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/arbitrum-token-bridge diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge new file mode 160000 index 0000000..2471056 --- /dev/null +++ b/lib/arbitrum-token-bridge @@ -0,0 +1 @@ +Subproject commit 2471056b009a6ff8f4b6c36e161bb6ee8021de6d From f371a1ad021c19011cad9f799c31263bcb92848f Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 12:15:31 +0300 Subject: [PATCH 17/57] Add more sanity checks --- deploy/FarmProxyInit.sol | 41 +++- deploy/L2FarmProxySpell.sol | 2 + test/Integration.t.sol | 66 +++-- test/mocks/DssVestMock.sol | 478 ++++++++++++++++++++++++++++++++++++ test/mocks/InboxMock.sol | 2 +- 5 files changed, 540 insertions(+), 49 deletions(-) create mode 100644 test/mocks/DssVestMock.sol diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 3631af8..d2e3c5a 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -20,12 +20,20 @@ import { DssInstance } from "dss-test/MCD.sol"; import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; +interface AuthLike { + function rely(address usr) external; +} + interface DssVestLike { + function gem() external view returns (address); function create(address _usr, uint256 _tot, uint256 _bgn, uint256 _tau, uint256 _eta, address _mgr) external returns (uint256 id); function restrict(uint256 _id) external; } interface VestedRewardsDistributionLike { + function dssVest() external view returns (address); + function stakingRewards() external view returns (address); + function gem() external view returns (address); function file(bytes32 what, uint256 data) external; } @@ -83,22 +91,33 @@ library FarmProxyInit { ProxiesConfig memory cfg ) internal { L1FarmProxyLike l1Proxy = L1FarmProxyLike(l1Proxy_); + DssVestLike vest = DssVestLike(cfg.vest); + VestedRewardsDistributionLike distribution = VestedRewardsDistributionLike(cfg.vestedRewardDistribution); // sanity checks - require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/rewards-token-mismatch"); - require(l1Proxy.l2Proxy() == cfg.l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); - require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); - require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); - require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); - require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); - require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); - require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); - require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); + require(vest.gem() == cfg.rewardsToken, "FarmProxyInit/vest-gem-mismatch"); + require(distribution.gem() == cfg.rewardsToken, "FarmProxyInit/distribution-gem-mismatch"); + require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); + require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); + require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/rewards-token-mismatch"); + require(l1Proxy.l2Proxy() == cfg.l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); + require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); + require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); + require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); + require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); + require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); + require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); // setup vest - DssVestLike vest = DssVestLike(cfg.vest); + if (cfg.rewardsToken == dss.chainlog.getAddress("NST")) { + // TODO: NST isn't currently planned as an L2 rewardsToken, so do we want to handle this case at all? + AuthLike(dss.chainlog.getAddress("MCD_VAT")).rely(cfg.vest); + } else { + AuthLike(cfg.rewardsToken).rely(cfg.vest); + } uint256 vestId = vest.create({ _usr: cfg.vestedRewardDistribution, _tot: cfg.vestTot, @@ -108,7 +127,7 @@ library FarmProxyInit { _mgr: cfg.vestMgr }); vest.restrict(vestId); - VestedRewardsDistributionLike(cfg.vestedRewardDistribution).file("vestId", vestId); + distribution.file("vestId", vestId); // setup L1 proxy diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index d738294..085c946 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -35,6 +35,8 @@ contract L2FarmProxySpell { function file(bytes32 what, uint256 data) external { l2Proxy.file(what, data); } function init(uint256 minReward, uint256 rewardsDuration) external { + // TODO: add L2-side sanity checks + l2Proxy.file("minReward", minReward); FarmLike farm = FarmLike(address(l2Proxy.farm())); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 91dbb55..1d9a7c4 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -26,12 +26,14 @@ import { TokenGatewayDeploy } from "lib/arbitrum-token-bridge/deploy/TokenGatewa import { L2TokenGatewaySpell } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewaySpell.sol"; import { L2TokenGatewayInstance } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewayInstance.sol"; import { TokenGatewayInit, GatewaysConfig, MessageParams as GatewayMessageParams } from "lib/arbitrum-token-bridge/deploy/TokenGatewayInit.sol"; -import { GemMock } from "lib/arbitrum-token-bridge/test/mocks/GemMock.sol"; import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; import { VestedRewardsDistribution } from "lib/endgame-toolkit/src/VestedRewardsDistribution.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; +import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; + import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; import { L2FarmProxyInstance } from "deploy/L2FarmProxyInstance.sol"; @@ -43,21 +45,6 @@ interface L1RelayLike { function l2GovernanceRelay() external view returns (address); } -interface GemLike { - function balanceOf(address) external view returns (uint256); -} - -interface DssVestLike { - function usr(uint256) external view returns (address); - function bgn(uint256) external view returns (uint256); - function clf(uint256) external view returns (uint256); - function fin(uint256) external view returns (uint256); - function mgr(uint256) external view returns (address); - function res(uint256) external view returns (uint256); - function tot(uint256) external view returns (uint256); - function unpaid(uint256 _id) external view returns (uint256); -} - contract IntegrationTest is DssTest { string config; Domain l1Domain; @@ -67,11 +54,11 @@ contract IntegrationTest is DssTest { DssInstance dss; address PAUSE_PROXY; address ESCROW; - address l1Token; + GemMock l1Token; address l1Gateway; address INBOX; L1FarmProxy l1Proxy; - DssVestLike vest; + DssVestMintableMock vest; uint256 vestId; VestedRewardsDistribution vestedRewardDistribution; @@ -119,7 +106,7 @@ contract IntegrationTest is DssTest { vm.label(address(l2Token), "l2Token"); address[] memory l1Tokens = new address[](1); - l1Tokens[0] = l1Token; + l1Tokens[0] = address(l1Token); address[] memory l2Tokens = new address[](1); l2Tokens[0] = address(l2Token); GatewayMessageParams memory xchainMsg = GatewayMessageParams({ @@ -151,10 +138,15 @@ contract IntegrationTest is DssTest { dss = l1Domain.dss(); PAUSE_PROXY = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); L2_GOV_RELAY = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")).l2GovernanceRelay(); - l1Token = dss.chainlog.getAddress("MCD_GOV"); // TODO: change to use NGT when deployed vm.label(address(PAUSE_PROXY), "PAUSE_PROXY"); vm.label(address(L2_GOV_RELAY), "L2_GOV_RELAY"); - vm.label(l1Token, "l1Token"); + + vm.startPrank(PAUSE_PROXY); + dss.chainlog.setAddress("NST", address(123)); // TODO: Remove after NST has been setup in a spell + l1Token = new GemMock(100 ether); + vest = new DssVestMintableMock(address(l1Token)); + vest.file("cap", type(uint256).max); + vm.stopPrank(); setupGateways(); @@ -179,14 +171,13 @@ contract IntegrationTest is DssTest { l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ deployer: address(this), owner: PAUSE_PROXY, - gem: l1Token, + gem: address(l1Token), l2Proxy: address(l2Proxy), feeRecipient: L2_GOV_RELAY, inbox: INBOX, l1Gateway: l1Gateway }))); - vest = DssVestLike(dss.chainlog.getAddress("MCD_VEST_MKR")); // TODO: change to use NGT vest when deployed VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ deployer: address(this), owner: PAUSE_PROXY, @@ -209,7 +200,7 @@ contract IntegrationTest is DssTest { vestBgn: block.timestamp, vestTau: 100 days, vestMgr: address(0), - rewardsToken: l1Token, + rewardsToken: address(l1Token), l2Proxy: address(l2Proxy), feeRecipient: L2_GOV_RELAY, inbox: INBOX, @@ -226,18 +217,19 @@ contract IntegrationTest is DssTest { vm.stopPrank(); // test L1 side of initProxies - assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), address(l1Proxy)); vestId = vestedRewardDistribution.vestId(); - assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); - assertEq(vest.tot(vestId), cfg.vestTot); - assertEq(vest.bgn(vestId), cfg.vestBgn); - assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); - assertEq(vest.clf(vestId), cfg.vestBgn); - assertEq(vest.mgr(vestId), cfg.vestMgr); - assertEq(vest.res(vestId), 1); - assertEq(l1Proxy.maxGas(), cfg.maxGas); - assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); - assertEq(l1Proxy.minReward(), cfg.l1MinReward); + assertEq(l1Token.wards(cfg.vest), 1); + assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); + assertEq(vest.tot(vestId), cfg.vestTot); + assertEq(vest.bgn(vestId), cfg.vestBgn); + assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); + assertEq(vest.clf(vestId), cfg.vestBgn); + assertEq(vest.mgr(vestId), cfg.vestMgr); + assertEq(vest.res(vestId), 1); + assertEq(l1Proxy.maxGas(), cfg.maxGas); + assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); + assertEq(l1Proxy.minReward(), cfg.l1MinReward); + assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), address(l1Proxy)); l2Domain.relayFromHost(true); @@ -255,11 +247,11 @@ contract IntegrationTest is DssTest { vm.warp(vest.bgn(vestId) + minReward * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId)); uint256 amount = vest.unpaid(vestId); assertGe(amount, minReward); - assertEq(GemLike(l1Token).balanceOf(ESCROW), 0); + assertEq(l1Token.balanceOf(ESCROW), 0); vestedRewardDistribution.distribute(); - assertEq(GemLike(l1Token).balanceOf(ESCROW), amount); + assertEq(l1Token.balanceOf(ESCROW), amount); l2Domain.relayFromHost(true); diff --git a/test/mocks/DssVestMock.sol b/test/mocks/DssVestMock.sol new file mode 100644 index 0000000..affdb1c --- /dev/null +++ b/test/mocks/DssVestMock.sol @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// DssVestMock - Mock of DssVest, a token vesting contract +// +// Copyright (C) 2021 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +interface MintLike { + function mint(address, uint256) external; +} + +interface ChainlogLike { + function getAddress(bytes32) external view returns (address); +} + +interface DaiJoinLike { + function exit(address, uint256) external; +} + +interface VatLike { + function hope(address) external; + function suck(address, address, uint256) external; + function live() external view returns (uint256); +} + +interface TokenLike { + function transferFrom(address, address, uint256) external returns (bool); +} + +abstract contract DssVestMock { + // --- Data --- + mapping (address => uint256) public wards; + + struct Award { + address usr; // Vesting recipient + uint48 bgn; // Start of vesting period [timestamp] + uint48 clf; // The cliff date [timestamp] + uint48 fin; // End of vesting period [timestamp] + address mgr; // A manager address that can yank + uint8 res; // Restricted + uint128 tot; // Total reward amount + uint128 rxd; // Amount of vest claimed + } + mapping (uint256 => Award) public awards; + + uint256 public cap; // Maximum per-second issuance token rate + + uint256 public ids; // Total vestings + + uint256 internal locked; + + uint256 public constant TWENTY_YEARS = 20 * 365 days; + + // --- Events --- + event Rely(address indexed usr); + event Deny(address indexed usr); + + event File(bytes32 indexed what, uint256 data); + + event Init(uint256 indexed id, address indexed usr); + event Vest(uint256 indexed id, uint256 amt); + event Restrict(uint256 indexed id); + event Unrestrict(uint256 indexed id); + event Yank(uint256 indexed id, uint256 end); + event Move(uint256 indexed id, address indexed dst); + + // Getters to access only to the value desired + function usr(uint256 _id) external view returns (address) { + return awards[_id].usr; + } + + function bgn(uint256 _id) external view returns (uint256) { + return awards[_id].bgn; + } + + function clf(uint256 _id) external view returns (uint256) { + return awards[_id].clf; + } + + function fin(uint256 _id) external view returns (uint256) { + return awards[_id].fin; + } + + function mgr(uint256 _id) external view returns (address) { + return awards[_id].mgr; + } + + function res(uint256 _id) external view returns (uint256) { + return awards[_id].res; + } + + function tot(uint256 _id) external view returns (uint256) { + return awards[_id].tot; + } + + function rxd(uint256 _id) external view returns (uint256) { + return awards[_id].rxd; + } + + /** + @dev Base vesting logic contract constructor + */ + constructor() { + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Mutex --- + modifier lock { + require(locked == 0, "DssVest/system-locked"); + locked = 1; + _; + locked = 0; + } + + // --- Auth --- + modifier auth { + require(wards[msg.sender] == 1, "DssVest/not-authorized"); + _; + } + + function rely(address _usr) external auth { + wards[_usr] = 1; + emit Rely(_usr); + } + function deny(address _usr) external auth { + wards[_usr] = 0; + emit Deny(_usr); + } + + /** + @dev (Required) Set the per-second token issuance rate. + @param what The tag of the value to change (ex. bytes32("cap")) + @param data The value to update (ex. cap of 1000 tokens/yr == 1000*WAD/365 days) + */ + function file(bytes32 what, uint256 data) external lock auth { + if (what == "cap") cap = data; // The maximum amount of tokens that can be streamed per-second per vest + else revert("DssVest/file-unrecognized-param"); + emit File(what, data); + } + + function min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x > y ? y : x; + } + function toUint48(uint256 x) internal pure returns (uint48 z) { + require((z = uint48(x)) == x, "DssVest/uint48-overflow"); + } + function toUint128(uint256 x) internal pure returns (uint128 z) { + require((z = uint128(x)) == x, "DssVest/uint128-overflow"); + } + + /** + @dev Govanance adds a vesting contract + @param _usr The recipient of the reward + @param _tot The total amount of the vest + @param _bgn The starting timestamp of the vest + @param _tau The duration of the vest (in seconds) + @param _eta The cliff duration in seconds (i.e. 1 years) + @param _mgr An optional manager for the contract. Can yank if vesting ends prematurely. + @return id The id of the vesting contract + */ + function create(address _usr, uint256 _tot, uint256 _bgn, uint256 _tau, uint256 _eta, address _mgr) external lock auth returns (uint256 id) { + require(_usr != address(0), "DssVest/invalid-user"); + require(_tot > 0, "DssVest/no-vest-total-amount"); + require(_bgn < block.timestamp + TWENTY_YEARS, "DssVest/bgn-too-far"); + require(_bgn > block.timestamp - TWENTY_YEARS, "DssVest/bgn-too-long-ago"); + require(_tau > 0, "DssVest/tau-zero"); + require(_tot / _tau <= cap, "DssVest/rate-too-high"); + require(_tau <= TWENTY_YEARS, "DssVest/tau-too-long"); + require(_eta <= _tau, "DssVest/eta-too-long"); + require(ids < type(uint256).max, "DssVest/ids-overflow"); + + unchecked { + id = ++ids; + } + awards[id] = Award({ + usr: _usr, + bgn: toUint48(_bgn), + clf: toUint48(_bgn + _eta), + fin: toUint48(_bgn + _tau), + tot: toUint128(_tot), + rxd: 0, + mgr: _mgr, + res: 0 + }); + emit Init(id, _usr); + } + + /** + @dev Anyone (or only owner of a vesting contract if restricted) calls this to claim all available rewards + @param _id The id of the vesting contract + */ + function vest(uint256 _id) external { + _vest(_id, type(uint256).max); + } + + /** + @dev Anyone (or only owner of a vesting contract if restricted) calls this to claim rewards + @param _id The id of the vesting contract + @param _maxAmt The maximum amount to vest + */ + function vest(uint256 _id, uint256 _maxAmt) external { + _vest(_id, _maxAmt); + } + + /** + @dev Anyone (or only owner of a vesting contract if restricted) calls this to claim rewards + @param _id The id of the vesting contract + @param _maxAmt The maximum amount to vest + */ + function _vest(uint256 _id, uint256 _maxAmt) internal lock { + Award memory _award = awards[_id]; + require(_award.usr != address(0), "DssVest/invalid-award"); + require(_award.res == 0 || _award.usr == msg.sender, "DssVest/only-user-can-claim"); + uint256 amt = unpaid(block.timestamp, _award.bgn, _award.clf, _award.fin, _award.tot, _award.rxd); + amt = min(amt, _maxAmt); + awards[_id].rxd = toUint128(_award.rxd + amt); + pay(_award.usr, amt); + emit Vest(_id, amt); + } + + /** + @dev amount of tokens accrued, not accounting for tokens paid + @param _id The id of the vesting contract + @return amt The accrued amount + */ + function accrued(uint256 _id) external view returns (uint256 amt) { + Award memory _award = awards[_id]; + require(_award.usr != address(0), "DssVest/invalid-award"); + amt = accrued(block.timestamp, _award.bgn, _award.fin, _award.tot); + } + + /** + @dev amount of tokens accrued, not accounting for tokens paid + @param _time The timestamp to perform the calculation + @param _bgn The start time of the contract + @param _fin The end time of the contract + @param _tot The total amount of the contract + @return amt The accrued amount + */ + function accrued(uint256 _time, uint48 _bgn, uint48 _fin, uint128 _tot) internal pure returns (uint256 amt) { + if (_time < _bgn) { + amt = 0; + } else if (_time >= _fin) { + amt = _tot; + } else { + amt = (_tot * (_time - _bgn)) / (_fin - _bgn); // 0 <= amt < _award.tot + } + } + + /** + @dev return the amount of vested, claimable GEM for a given ID + @param _id The id of the vesting contract + @return amt The claimable amount + */ + function unpaid(uint256 _id) external view returns (uint256 amt) { + Award memory _award = awards[_id]; + require(_award.usr != address(0), "DssVest/invalid-award"); + amt = unpaid(block.timestamp, _award.bgn, _award.clf, _award.fin, _award.tot, _award.rxd); + } + + /** + @dev amount of tokens accrued, not accounting for tokens paid + @param _time The timestamp to perform the calculation + @param _bgn The start time of the contract + @param _clf The timestamp of the cliff + @param _fin The end time of the contract + @param _tot The total amount of the contract + @param _rxd The number of gems received + @return amt The claimable amount + */ + function unpaid(uint256 _time, uint48 _bgn, uint48 _clf, uint48 _fin, uint128 _tot, uint128 _rxd) internal pure returns (uint256 amt) { + amt = _time < _clf ? 0 : accrued(_time, _bgn, _fin, _tot) - _rxd; + } + + /** + @dev Allows governance or the owner to restrict vesting to the owner only + @param _id The id of the vesting contract + */ + function restrict(uint256 _id) external lock { + address usr_ = awards[_id].usr; + require(usr_ != address(0), "DssVest/invalid-award"); + require(wards[msg.sender] == 1 || usr_ == msg.sender, "DssVest/not-authorized"); + awards[_id].res = 1; + emit Restrict(_id); + } + + /** + @dev Allows governance or the owner to enable permissionless vesting + @param _id The id of the vesting contract + */ + function unrestrict(uint256 _id) external lock { + address usr_ = awards[_id].usr; + require(usr_ != address(0), "DssVest/invalid-award"); + require(wards[msg.sender] == 1 || usr_ == msg.sender, "DssVest/not-authorized"); + awards[_id].res = 0; + emit Unrestrict(_id); + } + + /** + @dev Allows governance or the manager to remove a vesting contract immediately + @param _id The id of the vesting contract + */ + function yank(uint256 _id) external { + _yank(_id, block.timestamp); + } + + /** + @dev Allows governance or the manager to remove a vesting contract at a future time + @param _id The id of the vesting contract + @param _end A scheduled time to end the vest + */ + function yank(uint256 _id, uint256 _end) external { + _yank(_id, _end); + } + + /** + @dev Allows governance or the manager to end pre-maturely a vesting contract + @param _id The id of the vesting contract + @param _end A scheduled time to end the vest + */ + function _yank(uint256 _id, uint256 _end) internal lock { + require(wards[msg.sender] == 1 || awards[_id].mgr == msg.sender, "DssVest/not-authorized"); + Award memory _award = awards[_id]; + require(_award.usr != address(0), "DssVest/invalid-award"); + if (_end < block.timestamp) { + _end = block.timestamp; + } + if (_end < _award.fin) { + uint48 end = toUint48(_end); + awards[_id].fin = end; + if (end < _award.bgn) { + awards[_id].bgn = end; + awards[_id].clf = end; + awards[_id].tot = 0; + } else if (end < _award.clf) { + awards[_id].clf = end; + awards[_id].tot = 0; + } else { + awards[_id].tot = toUint128( + unpaid(_end, _award.bgn, _award.clf, _award.fin, _award.tot, _award.rxd) + _award.rxd + ); + } + } + + emit Yank(_id, _end); + } + + /** + @dev Allows owner to move a contract to a different address + @param _id The id of the vesting contract + @param _dst The address to send ownership of the contract to + */ + function move(uint256 _id, address _dst) external lock { + require(awards[_id].usr == msg.sender, "DssVest/only-user-can-move"); + require(_dst != address(0), "DssVest/zero-address-invalid"); + awards[_id].usr = _dst; + emit Move(_id, _dst); + } + + /** + @dev Return true if a contract is valid + @param _id The id of the vesting contract + @return isValid True for valid contract + */ + function valid(uint256 _id) external view returns (bool isValid) { + isValid = awards[_id].rxd < awards[_id].tot; + } + + /** + @dev Override this to implement payment logic. + @param _guy The payment target. + @param _amt The payment amount. [units are implementation-specific] + */ + function pay(address _guy, uint256 _amt) virtual internal; +} + +contract DssVestMintableMock is DssVestMock { + + MintLike public immutable gem; + + /** + @dev This contract must be authorized to 'mint' on the token + @param _gem The contract address of the mintable token + */ + constructor(address _gem) DssVestMock() { + require(_gem != address(0), "DssVestMintable/Invalid-token-address"); + gem = MintLike(_gem); + } + + /** + @dev Override pay to handle mint logic + @param _guy The recipient of the minted token + @param _amt The amount of token units to send to the _guy + */ + function pay(address _guy, uint256 _amt) override internal { + gem.mint(_guy, _amt); + } +} + +contract DssVestSuckableMock is DssVestMock { + + uint256 internal constant RAY = 10**27; + + ChainlogLike public immutable chainlog; + VatLike public immutable vat; + DaiJoinLike public immutable daiJoin; + + /** + @dev This contract must be authorized to 'suck' on the vat + @param _chainlog The contract address of the MCD chainlog + */ + constructor(address _chainlog) DssVestMock() { + require(_chainlog != address(0), "DssVestSuckable/Invalid-chainlog-address"); + ChainlogLike chainlog_ = chainlog = ChainlogLike(_chainlog); + VatLike vat_ = vat = VatLike(chainlog_.getAddress("MCD_VAT")); + DaiJoinLike daiJoin_ = daiJoin = DaiJoinLike(chainlog_.getAddress("MCD_JOIN_DAI")); + + vat_.hope(address(daiJoin_)); + } + + /** + @dev Override pay to handle suck logic + @param _guy The recipient of the ERC-20 Dai + @param _amt The amount of Dai to send to the _guy [WAD] + */ + function pay(address _guy, uint256 _amt) override internal { + require(vat.live() == 1, "DssVestSuckable/vat-not-live"); + vat.suck(chainlog.getAddress("MCD_VOW"), address(this), _amt * RAY); + daiJoin.exit(_guy, _amt); + } +} + +/* + Transferrable token DssVest. Can be used to enable streaming payments of + any arbitrary token from an address (i.e. CU multisig) to individual + contributors. +*/ +contract DssVestTransferrableMock is DssVestMock { + + address public immutable czar; + TokenLike public immutable gem; + + /** + @dev This contract must be approved for transfer of the gem on the czar + @param _czar The owner of the tokens to be distributed + @param _gem The token to be distributed + */ + constructor(address _czar, address _gem) DssVestMock() { + require(_czar != address(0), "DssVestTransferrable/Invalid-distributor-address"); + require(_gem != address(0), "DssVestTransferrable/Invalid-token-address"); + czar = _czar; + gem = TokenLike(_gem); + } + + /** + @dev Override pay to handle transfer logic + @param _guy The recipient of the ERC-20 Dai + @param _amt The amount of gem to send to the _guy (in native token units) + */ + function pay(address _guy, uint256 _amt) override internal { + require(gem.transferFrom(czar, _guy, _amt), "DssVestTransferrable/failed-transfer"); + } +} diff --git a/test/mocks/InboxMock.sol b/test/mocks/InboxMock.sol index 71d2450..0833e88 100644 --- a/test/mocks/InboxMock.sol +++ b/test/mocks/InboxMock.sol @@ -8,7 +8,7 @@ contract InboxMock { function calculateRetryableSubmissionFee( uint256 dataLength, uint256 baseFee - ) external view returns (uint256 fee) { + ) external pure returns (uint256 fee) { fee = (1400 + 6 * dataLength) * (baseFee == 0 ? 30 gwei : baseFee); } } From 9b4847bc33a0d791e1ef51e063d2346bccbfe9e4 Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 14:18:25 +0300 Subject: [PATCH 18/57] Assume dss-vest already inited --- deploy/FarmProxyInit.sol | 10 ---------- test/Integration.t.sol | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index d2e3c5a..fc8f73b 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -20,10 +20,6 @@ import { DssInstance } from "dss-test/MCD.sol"; import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; -interface AuthLike { - function rely(address usr) external; -} - interface DssVestLike { function gem() external view returns (address); function create(address _usr, uint256 _tot, uint256 _bgn, uint256 _tau, uint256 _eta, address _mgr) external returns (uint256 id); @@ -112,12 +108,6 @@ library FarmProxyInit { // setup vest - if (cfg.rewardsToken == dss.chainlog.getAddress("NST")) { - // TODO: NST isn't currently planned as an L2 rewardsToken, so do we want to handle this case at all? - AuthLike(dss.chainlog.getAddress("MCD_VAT")).rely(cfg.vest); - } else { - AuthLike(cfg.rewardsToken).rely(cfg.vest); - } uint256 vestId = vest.create({ _usr: cfg.vestedRewardDistribution, _tot: cfg.vestTot, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 1d9a7c4..62718db 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -142,9 +142,9 @@ contract IntegrationTest is DssTest { vm.label(address(L2_GOV_RELAY), "L2_GOV_RELAY"); vm.startPrank(PAUSE_PROXY); - dss.chainlog.setAddress("NST", address(123)); // TODO: Remove after NST has been setup in a spell l1Token = new GemMock(100 ether); vest = new DssVestMintableMock(address(l1Token)); + l1Token.rely(address(vest)); vest.file("cap", type(uint256).max); vm.stopPrank(); @@ -218,7 +218,6 @@ contract IntegrationTest is DssTest { // test L1 side of initProxies vestId = vestedRewardDistribution.vestId(); - assertEq(l1Token.wards(cfg.vest), 1); assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); assertEq(vest.tot(vestId), cfg.vestTot); assertEq(vest.bgn(vestId), cfg.vestBgn); From 63e4ca268ca915ef0baad2041c6e4e5e2e675c7f Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 14:32:47 +0300 Subject: [PATCH 19/57] Update chainlog --- deploy/FarmProxyInit.sol | 5 ++++- test/Integration.t.sol | 27 +++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index fc8f73b..5f89375 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -77,6 +77,8 @@ struct ProxiesConfig { uint256 l2MinReward; // For the L2 proxy uint256 rewardsDuration; // For the farm on L2 MessageParams xchainMsg; // For the xchain message executing the L2 spell + bytes32 proxyChainlogKey; + bytes32 distrChainlogKey; } library FarmProxyInit { @@ -145,6 +147,7 @@ library FarmProxyInit { // update chainlog - dss.chainlog.setAddress("ARBITRUM_L1_FARM_PROXY", l1Proxy_); + dss.chainlog.setAddress(cfg.proxyChainlogKey, l1Proxy_); + dss.chainlog.setAddress(cfg.distrChainlogKey, cfg.vestedRewardDistribution); } } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 62718db..85b57ea 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -210,7 +210,9 @@ contract IntegrationTest is DssTest { l1MinReward: 0, l2MinReward: 1 ether, rewardsDuration: 1 days, - xchainMsg: xchainMsg + xchainMsg: xchainMsg, + proxyChainlogKey: "FARM_PROXY_TKA_TKB_ARB", + distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB" }); vm.startPrank(PAUSE_PROXY); FarmProxyInit.initProxies(dss, address(l1Proxy), l2ProxyInstance, cfg); @@ -218,17 +220,18 @@ contract IntegrationTest is DssTest { // test L1 side of initProxies vestId = vestedRewardDistribution.vestId(); - assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); - assertEq(vest.tot(vestId), cfg.vestTot); - assertEq(vest.bgn(vestId), cfg.vestBgn); - assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); - assertEq(vest.clf(vestId), cfg.vestBgn); - assertEq(vest.mgr(vestId), cfg.vestMgr); - assertEq(vest.res(vestId), 1); - assertEq(l1Proxy.maxGas(), cfg.maxGas); - assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); - assertEq(l1Proxy.minReward(), cfg.l1MinReward); - assertEq(dss.chainlog.getAddress("ARBITRUM_L1_FARM_PROXY"), address(l1Proxy)); + assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); + assertEq(vest.tot(vestId), cfg.vestTot); + assertEq(vest.bgn(vestId), cfg.vestBgn); + assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); + assertEq(vest.clf(vestId), cfg.vestBgn); + assertEq(vest.mgr(vestId), cfg.vestMgr); + assertEq(vest.res(vestId), 1); + assertEq(l1Proxy.maxGas(), cfg.maxGas); + assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); + assertEq(l1Proxy.minReward(), cfg.l1MinReward); + assertEq(dss.chainlog.getAddress("FARM_PROXY_TKA_TKB_ARB"), address(l1Proxy)); + assertEq(dss.chainlog.getAddress("REWARDS_DISTRIBUTION_TKA_TKB_ARB"), cfg.vestedRewardDistribution); l2Domain.relayFromHost(true); From 84e2b6af2e2b78700cdf5750ede0aa29c1fac55c Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 15:22:40 +0300 Subject: [PATCH 20/57] Make L2 spell reusable across L2 proxies --- deploy/FarmProxyDeploy.sol | 15 +++---- deploy/FarmProxyInit.sol | 66 +++++++++++++++-------------- deploy/L2FarmProxyInstance.sol | 22 ---------- deploy/L2FarmProxySpell.sol | 38 ++++++++++------- src/L1FarmProxy.sol | 5 ++- test/Integration.t.sol | 70 +++++++++++++++---------------- test/L1FarmProxy.t.sol | 20 ++++----- test/mocks/L1TokenGatewayMock.sol | 3 ++ 8 files changed, 115 insertions(+), 124 deletions(-) delete mode 100644 deploy/L2FarmProxyInstance.sol diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol index e7c5468..ec94790 100644 --- a/deploy/FarmProxyDeploy.sol +++ b/deploy/FarmProxyDeploy.sol @@ -18,7 +18,6 @@ pragma solidity >=0.8.0; import { ScriptTools } from "dss-test/ScriptTools.sol"; -import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; import { L1FarmProxy } from "src/L1FarmProxy.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; @@ -30,10 +29,9 @@ library FarmProxyDeploy { address gem, address l2Proxy, address feeRecipient, - address inbox, address l1Gateway ) internal returns (address l1Proxy) { - l1Proxy = address(new L1FarmProxy(gem, l2Proxy, feeRecipient, inbox, l1Gateway)); + l1Proxy = address(new L1FarmProxy(gem, l2Proxy, feeRecipient, l1Gateway)); ScriptTools.switchOwner(l1Proxy, deployer, owner); } @@ -41,9 +39,12 @@ library FarmProxyDeploy { address deployer, address owner, address farm - ) internal returns (L2FarmProxyInstance memory l2ProxyInstance) { - l2ProxyInstance.proxy = address(new L2FarmProxy(farm)); - l2ProxyInstance.spell = address(new L2FarmProxySpell(l2ProxyInstance.proxy)); - ScriptTools.switchOwner(l2ProxyInstance.proxy, deployer, owner); + ) internal returns (address l2Proxy) { + l2Proxy = address(new L2FarmProxy(farm)); + ScriptTools.switchOwner(l2Proxy, deployer, owner); + } + + function deployL2ProxySpell() internal returns (address l2Spell) { + l2Spell = address(new L2FarmProxySpell()); } } diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 5f89375..32ceb00 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -17,7 +17,6 @@ pragma solidity >=0.8.0; import { DssInstance } from "dss-test/MCD.sol"; -import { L2FarmProxyInstance } from "./L2FarmProxyInstance.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; interface DssVestLike { @@ -37,7 +36,6 @@ interface L1FarmProxyLike { function rewardsToken() external view returns (address); function l2Proxy() external view returns (address); function feeRecipient() external view returns (address); - function inbox() external view returns (address); function l1Gateway() external view returns (address); function file(bytes32 what, uint256 data) external; } @@ -61,57 +59,57 @@ struct MessageParams { struct ProxiesConfig { address vest; - address vestedRewardDistribution; uint256 vestTot; uint256 vestBgn; uint256 vestTau; address vestMgr; - address rewardsToken; - address l2Proxy; + address vestedRewardsDistribution; + address l1RewardsToken; + address l2RewardsToken; address feeRecipient; - address inbox; address l1Gateway; uint256 maxGas; // For the L1 proxy uint256 gasPriceBid; // For the L1 proxy uint256 l1MinReward; // For the L1 proxy uint256 l2MinReward; // For the L2 proxy - uint256 rewardsDuration; // For the farm on L2 + address farm; // The L2 farm + uint256 rewardsDuration; // For the L2 farm MessageParams xchainMsg; // For the xchain message executing the L2 spell - bytes32 proxyChainlogKey; - bytes32 distrChainlogKey; + bytes32 proxyChainlogKey; // Chainlog key for the L1 proxy + bytes32 distrChainlogKey; // Chainlog key for vestedRewardsDistribution } library FarmProxyInit { function initProxies( - DssInstance memory dss, - address l1Proxy_, - L2FarmProxyInstance memory l2ProxyInstance, - ProxiesConfig memory cfg + DssInstance memory dss, + address l1Proxy_, + address l2Proxy, + address l2Spell, + ProxiesConfig memory cfg ) internal { L1FarmProxyLike l1Proxy = L1FarmProxyLike(l1Proxy_); DssVestLike vest = DssVestLike(cfg.vest); - VestedRewardsDistributionLike distribution = VestedRewardsDistributionLike(cfg.vestedRewardDistribution); + VestedRewardsDistributionLike distribution = VestedRewardsDistributionLike(cfg.vestedRewardsDistribution); // sanity checks - require(vest.gem() == cfg.rewardsToken, "FarmProxyInit/vest-gem-mismatch"); - require(distribution.gem() == cfg.rewardsToken, "FarmProxyInit/distribution-gem-mismatch"); - require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); - require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); - require(l1Proxy.rewardsToken() == cfg.rewardsToken, "FarmProxyInit/rewards-token-mismatch"); - require(l1Proxy.l2Proxy() == cfg.l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); - require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); - require(l1Proxy.inbox() == cfg.inbox, "FarmProxyInit/inbox-mismatch"); - require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); - require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); - require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); - require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); - require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); + require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); + require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); + require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); + require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); + require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); + require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); + require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); + require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); + require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); + require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); + require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); // setup vest uint256 vestId = vest.create({ - _usr: cfg.vestedRewardDistribution, + _usr: cfg.vestedRewardsDistribution, _tot: cfg.vestTot, _bgn: cfg.vestBgn, _tau: cfg.vestTau, @@ -137,8 +135,14 @@ library FarmProxyInit { require(address(l1GovRelay).balance >= l1CallValue, "FarmProxyInit/insufficient-relay-balance"); l1GovRelay.relay({ - target: l2ProxyInstance.spell, - targetData: abi.encodeCall(L2FarmProxySpell.init, (cfg.l2MinReward, cfg.rewardsDuration)), + target: l2Spell, + targetData: abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + cfg.l2RewardsToken, + cfg.farm, + cfg.l2MinReward, + cfg.rewardsDuration + )), l1CallValue: l1CallValue, maxGas: cfg.xchainMsg.maxGas, gasPriceBid: cfg.xchainMsg.gasPriceBid, @@ -148,6 +152,6 @@ library FarmProxyInit { // update chainlog dss.chainlog.setAddress(cfg.proxyChainlogKey, l1Proxy_); - dss.chainlog.setAddress(cfg.distrChainlogKey, cfg.vestedRewardDistribution); + dss.chainlog.setAddress(cfg.distrChainlogKey, cfg.vestedRewardsDistribution); } } diff --git a/deploy/L2FarmProxyInstance.sol b/deploy/L2FarmProxyInstance.sol deleted file mode 100644 index 4aeffba..0000000 --- a/deploy/L2FarmProxyInstance.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity >=0.8.0; - -struct L2FarmProxyInstance { - address proxy; - address spell; -} diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index 085c946..ba63f1a 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -16,31 +16,39 @@ pragma solidity >=0.8.0; -import { L2FarmProxy } from "src/L2FarmProxy.sol"; +interface L2FarmProxyLike { + function rewardsToken() external view returns (address); + function farm() external view returns (address); + function rely(address) external; + function deny(address) external; + function file(bytes32, uint256) external; +} interface FarmLike { function setRewardsDistribution(address) external; function setRewardsDuration(uint256) external; } -// A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2FarmProxy +// A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2 farm proxies contract L2FarmProxySpell { - L2FarmProxy public immutable l2Proxy; - constructor(address l2Proxy_) { - l2Proxy = L2FarmProxy(l2Proxy_); - } - function rely(address usr) external { l2Proxy.rely(usr); } - function deny(address usr) external { l2Proxy.deny(usr); } - function file(bytes32 what, uint256 data) external { l2Proxy.file(what, data); } + function rely(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).rely(usr); } + function deny(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).deny(usr); } + function file(address l2Proxy, bytes32 what, uint256 data) external { L2FarmProxyLike(l2Proxy).file(what, data); } - function init(uint256 minReward, uint256 rewardsDuration) external { - // TODO: add L2-side sanity checks + function init( + address l2Proxy, + address rewardsToken, + address farm, + uint256 minReward, + uint256 rewardsDuration + ) external { + require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); + require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); - l2Proxy.file("minReward", minReward); + L2FarmProxyLike(l2Proxy).file("minReward", minReward); - FarmLike farm = FarmLike(address(l2Proxy.farm())); - farm.setRewardsDistribution(address(l2Proxy)); - farm.setRewardsDuration(rewardsDuration); + FarmLike(farm).setRewardsDistribution(l2Proxy); + FarmLike(farm).setRewardsDuration(rewardsDuration); } } diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 2fe0376..825a0fa 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -22,6 +22,7 @@ interface GemLike { } interface L1TokenGatewayLike { + function inbox() external view returns (address); function outboundTransferCustomRefund( address l1Token, address refundTo, @@ -53,12 +54,12 @@ contract L1FarmProxy { event Deny(address indexed usr); event File(bytes32 indexed what, uint256 data); - constructor(address _rewardsToken, address _l2Proxy, address _feeRecipient, address _inbox, address _l1Gateway) { + constructor(address _rewardsToken, address _l2Proxy, address _feeRecipient, address _l1Gateway) { rewardsToken = _rewardsToken; l2Proxy = _l2Proxy; feeRecipient = _feeRecipient; - inbox = InboxLike(_inbox); l1Gateway = L1TokenGatewayLike(_l1Gateway); + inbox = InboxLike(l1Gateway.inbox()); GemLike(_rewardsToken).approve(_l1Gateway, type(uint256).max); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 85b57ea..2455087 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -36,7 +36,6 @@ import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; -import { L2FarmProxyInstance } from "deploy/L2FarmProxyInstance.sol"; import { FarmProxyInit, ProxiesConfig, MessageParams as ProxyMessageParams } from "deploy/FarmProxyInit.sol"; import { L1FarmProxy } from "src/L1FarmProxy.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; @@ -56,11 +55,10 @@ contract IntegrationTest is DssTest { address ESCROW; GemMock l1Token; address l1Gateway; - address INBOX; L1FarmProxy l1Proxy; DssVestMintableMock vest; uint256 vestId; - VestedRewardsDistribution vestedRewardDistribution; + VestedRewardsDistribution vestedRewardsDistribution; // L2-side address L2_GOV_RELAY; @@ -74,8 +72,8 @@ contract IntegrationTest is DssTest { vm.label(address(ESCROW), "ESCROW"); l2Domain = new ArbitrumDomain(config, getChain("arbitrum_one"), l1Domain); - INBOX = l2Domain.readConfigAddress("inbox"); - vm.label(INBOX, "INBOX"); + address inbox = l2Domain.readConfigAddress("inbox"); + vm.label(inbox, "INBOX"); address l1Gateway_ = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 2); // foundry increments a global nonce across domains l2Domain.selectFork(); @@ -94,7 +92,7 @@ contract IntegrationTest is DssTest { owner: PAUSE_PROXY, l2Gateway: address(l2Gateway), l1Router: address(0), - inbox: INBOX, + inbox: inbox, escrow: ESCROW }); assertEq(address(l1Gateway), l1Gateway_); @@ -117,7 +115,7 @@ contract IntegrationTest is DssTest { GatewaysConfig memory cfg = GatewaysConfig({ counterpartGateway: address(l2Gateway), l1Router: address(0), - inbox: INBOX, + inbox: inbox, l1Tokens: l1Tokens, l2Tokens: l2Tokens, xchainMsg: xchainMsg @@ -159,13 +157,12 @@ contract IntegrationTest is DssTest { }); farm = StakingRewards(StakingRewardsDeploy.deploy(farmParams)); - L2FarmProxyInstance memory l2ProxyInstance = FarmProxyDeploy.deployL2Proxy({ + l2Proxy = L2FarmProxy(FarmProxyDeploy.deployL2Proxy({ deployer: address(this), owner: L2_GOV_RELAY, farm: address(farm) - }); - l2Proxy = L2FarmProxy(l2ProxyInstance.proxy); - assertEq(address(L2FarmProxySpell(l2ProxyInstance.spell).l2Proxy()), address(l2Proxy)); + })); + address l2Spell = FarmProxyDeploy.deployL2ProxySpell(); l1Domain.selectFork(); l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ @@ -174,7 +171,6 @@ contract IntegrationTest is DssTest { gem: address(l1Token), l2Proxy: address(l2Proxy), feeRecipient: L2_GOV_RELAY, - inbox: INBOX, l1Gateway: l1Gateway }))); @@ -184,7 +180,7 @@ contract IntegrationTest is DssTest { vest: address(vest), rewards: address(l1Proxy) }); - vestedRewardDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams)); + vestedRewardsDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams)); (bool success,) = address(l1Proxy).call{value: 1 ether}(""); assertTrue(success); @@ -194,33 +190,33 @@ contract IntegrationTest is DssTest { maxSubmissionCost: 0.01 ether }); ProxiesConfig memory cfg = ProxiesConfig({ - vest: address(vest), - vestedRewardDistribution: address(vestedRewardDistribution), - vestTot: 100 * 1e18, - vestBgn: block.timestamp, - vestTau: 100 days, - vestMgr: address(0), - rewardsToken: address(l1Token), - l2Proxy: address(l2Proxy), - feeRecipient: L2_GOV_RELAY, - inbox: INBOX, - l1Gateway: l1Gateway, - maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin - gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor - l1MinReward: 0, - l2MinReward: 1 ether, - rewardsDuration: 1 days, - xchainMsg: xchainMsg, - proxyChainlogKey: "FARM_PROXY_TKA_TKB_ARB", - distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB" + vest: address(vest), + vestTot: 100 * 1e18, + vestBgn: block.timestamp, + vestTau: 100 days, + vestMgr: address(0), + vestedRewardsDistribution: address(vestedRewardsDistribution), + l1RewardsToken: address(l1Token), + l2RewardsToken: address(l2Token), + feeRecipient: L2_GOV_RELAY, + l1Gateway: l1Gateway, + maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin + gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor + l1MinReward: 0, + l2MinReward: 1 ether, + farm: address(farm), + rewardsDuration: 1 days, + xchainMsg: xchainMsg, + proxyChainlogKey: "FARM_PROXY_TKA_TKB_ARB", + distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB" }); vm.startPrank(PAUSE_PROXY); - FarmProxyInit.initProxies(dss, address(l1Proxy), l2ProxyInstance, cfg); + FarmProxyInit.initProxies(dss, address(l1Proxy), address(l2Proxy), l2Spell, cfg); vm.stopPrank(); // test L1 side of initProxies - vestId = vestedRewardDistribution.vestId(); - assertEq(vest.usr(vestId), cfg.vestedRewardDistribution); + vestId = vestedRewardsDistribution.vestId(); + assertEq(vest.usr(vestId), cfg.vestedRewardsDistribution); assertEq(vest.tot(vestId), cfg.vestTot); assertEq(vest.bgn(vestId), cfg.vestBgn); assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); @@ -231,7 +227,7 @@ contract IntegrationTest is DssTest { assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); assertEq(l1Proxy.minReward(), cfg.l1MinReward); assertEq(dss.chainlog.getAddress("FARM_PROXY_TKA_TKB_ARB"), address(l1Proxy)); - assertEq(dss.chainlog.getAddress("REWARDS_DISTRIBUTION_TKA_TKB_ARB"), cfg.vestedRewardDistribution); + assertEq(dss.chainlog.getAddress("REWARDS_DISTRIBUTION_TKA_TKB_ARB"), cfg.vestedRewardsDistribution); l2Domain.relayFromHost(true); @@ -251,7 +247,7 @@ contract IntegrationTest is DssTest { assertGe(amount, minReward); assertEq(l1Token.balanceOf(ESCROW), 0); - vestedRewardDistribution.distribute(); + vestedRewardsDistribution.distribute(); assertEq(l1Token.balanceOf(ESCROW), amount); diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index c97bc4a..000077f 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -36,23 +36,23 @@ contract L1FarmProxyTest is DssTest { function setUp() public { inbox = address(new InboxMock()); - gateway = address(new L1TokenGatewayMock(escrow)); + gateway = address(new L1TokenGatewayMock(inbox, escrow)); rewardsToken = new GemMock(1_000_000 ether); - l1Proxy = new L1FarmProxy(address(rewardsToken), l2Proxy, feeRecipient, inbox, gateway); + l1Proxy = new L1FarmProxy(address(rewardsToken), l2Proxy, feeRecipient, gateway); } function testConstructor() public { vm.expectEmit(true, true, true, true); emit Rely(address(this)); - L1FarmProxy g = new L1FarmProxy(address(rewardsToken), l2Proxy, feeRecipient, inbox, gateway); + L1FarmProxy p = new L1FarmProxy(address(rewardsToken), l2Proxy, feeRecipient, gateway); - assertEq(g.rewardsToken(), address(rewardsToken)); - assertEq(g.l2Proxy(), l2Proxy); - assertEq(g.feeRecipient(), feeRecipient); - assertEq(address(g.inbox()), inbox); - assertEq(address(g.l1Gateway()), gateway); - assertEq(rewardsToken.allowance(address(g), gateway), type(uint256).max); - assertEq(g.wards(address(this)), 1); + assertEq(p.rewardsToken(), address(rewardsToken)); + assertEq(p.l2Proxy(), l2Proxy); + assertEq(p.feeRecipient(), feeRecipient); + assertEq(address(p.l1Gateway()), gateway); + assertEq(address(p.inbox()), inbox); + assertEq(rewardsToken.allowance(address(p), gateway), type(uint256).max); + assertEq(p.wards(address(this)), 1); } function testAuth() public { diff --git a/test/mocks/L1TokenGatewayMock.sol b/test/mocks/L1TokenGatewayMock.sol index fa2b9d4..6181bdc 100644 --- a/test/mocks/L1TokenGatewayMock.sol +++ b/test/mocks/L1TokenGatewayMock.sol @@ -22,11 +22,14 @@ interface TokenLike { } contract L1TokenGatewayMock { + address public immutable inbox; address public immutable escrow; constructor( + address _inbox, address _escrow ) { + inbox = _inbox; escrow = _escrow; } From 6044525f3adfa364150acab8ddc710d787e590c5 Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 16:16:25 +0300 Subject: [PATCH 21/57] Add farm sanity checks --- deploy/FarmProxyInit.sol | 2 ++ deploy/L2FarmProxySpell.sol | 19 ++++++++++++++++--- test/Integration.t.sol | 4 +++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 32ceb00..8c41dda 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -66,6 +66,7 @@ struct ProxiesConfig { address vestedRewardsDistribution; address l1RewardsToken; address l2RewardsToken; + address stakingToken; address feeRecipient; address l1Gateway; uint256 maxGas; // For the L1 proxy @@ -139,6 +140,7 @@ library FarmProxyInit { targetData: abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, cfg.l2RewardsToken, + cfg.stakingToken, cfg.farm, cfg.l2MinReward, cfg.rewardsDuration diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index ba63f1a..e69815a 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -25,26 +25,39 @@ interface L2FarmProxyLike { } interface FarmLike { - function setRewardsDistribution(address) external; + function rewardsToken() external view returns (address); + function stakingToken() external view returns (address); + function nominateNewOwner(address) external; + function setPaused(bool) external; + function recoverERC20(address, uint256) external; function setRewardsDuration(uint256) external; + function setRewardsDistribution(address) external; } -// A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2 farm proxies +// A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2 farms and their proxies contract L2FarmProxySpell { - function rely(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).rely(usr); } function deny(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).deny(usr); } function file(address l2Proxy, bytes32 what, uint256 data) external { L2FarmProxyLike(l2Proxy).file(what, data); } + function nominateNewOwner(address farm, address owner) external { FarmLike(farm).nominateNewOwner(owner); } + function setPaused(address farm, bool paused) external { FarmLike(farm).setPaused(paused); } + function recoverERC20(address farm, address token, uint256 amount) external { FarmLike(farm).recoverERC20(token, amount); } + function setRewardsDuration(address farm, uint256 rewardsDuration) external { FarmLike(farm).setRewardsDuration(rewardsDuration); } + function setRewardsDistribution(address farm, address rewardsDistribution) external { FarmLike(farm).setRewardsDistribution(rewardsDistribution); } + function init( address l2Proxy, address rewardsToken, + address stakingToken, address farm, uint256 minReward, uint256 rewardsDuration ) external { require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); + require(FarmLike(farm).rewardsToken() == rewardsToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); + require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); L2FarmProxyLike(l2Proxy).file("minReward", minReward); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 2455087..86650e9 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -150,9 +150,10 @@ contract IntegrationTest is DssTest { l2Domain.selectFork(); + address stakingToken = address(new GemMock(100 ether)); StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ owner: L2_GOV_RELAY, - stakingToken: address(new GemMock(100 ether)), + stakingToken: stakingToken, rewardsToken: address(l2Token) }); farm = StakingRewards(StakingRewardsDeploy.deploy(farmParams)); @@ -198,6 +199,7 @@ contract IntegrationTest is DssTest { vestedRewardsDistribution: address(vestedRewardsDistribution), l1RewardsToken: address(l1Token), l2RewardsToken: address(l2Token), + stakingToken: stakingToken, feeRecipient: L2_GOV_RELAY, l1Gateway: l1Gateway, maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin From 87a21476e6f2aa1b4e67dba4e4d0512970d9faae Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 21:59:02 +0300 Subject: [PATCH 22/57] Add more farm checks --- deploy/FarmProxyInit.sol | 2 +- deploy/L2FarmProxySpell.sol | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 8c41dda..c06be52 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -58,7 +58,7 @@ struct MessageParams { } struct ProxiesConfig { - address vest; + address vest; // DssVest, assumed to have been fully init'ed for l1RewardsToken uint256 vestTot; uint256 vestBgn; uint256 vestTau; diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index e69815a..92f8506 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -54,10 +54,12 @@ contract L2FarmProxySpell { uint256 minReward, uint256 rewardsDuration ) external { + // sanity checks require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); require(FarmLike(farm).rewardsToken() == rewardsToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); + require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token"); L2FarmProxyLike(l2Proxy).file("minReward", minReward); From cf292ac38150f9a70291783622985d892a15ea0a Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 25 Jun 2024 22:30:50 +0300 Subject: [PATCH 23/57] Fix nits --- test/L1FarmProxy.t.sol | 6 ++++-- test/L2FarmProxy.t.sol | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index 000077f..9f4564d 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -72,7 +72,7 @@ contract L1FarmProxyTest is DssTest { } function testReclaim() public { - (bool success,) = address(l1Proxy).call{value: 1 ether}(""); // not using deal() here, so as to check payable fallback + (bool success,) = address(l1Proxy).call{value: 1 ether}(""); // not using deal() here, so as to check receive() assertTrue(success); address to = address(0x123); uint256 proxyBefore = address(l1Proxy).balance; @@ -84,7 +84,7 @@ contract L1FarmProxyTest is DssTest { assertEq(address(l1Proxy).balance, proxyBefore - 0.2 ether); vm.expectRevert("L1FarmProxy/failed-to-send-ether"); - l1Proxy.reclaim(address(this), 0.2 ether); // no fallback + l1Proxy.reclaim(to, 1 ether); // insufficient balance } function testNotifyRewardAmount() public { @@ -99,6 +99,8 @@ contract L1FarmProxyTest is DssTest { (bool success,) = address(l1Proxy).call{value: 1 ether}(""); assertTrue(success); rewardsToken.transfer(address(l1Proxy), 1000 ether); + assertEq(rewardsToken.balanceOf(escrow), 0); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), 1000 ether); uint256 ethBefore = address(l1Proxy).balance; (uint256 l1CallValue,) = l1Proxy.estimateDepositCost(0, 0, 0); diff --git a/test/L2FarmProxy.t.sol b/test/L2FarmProxy.t.sol index a5208b9..7039c9d 100644 --- a/test/L2FarmProxy.t.sol +++ b/test/L2FarmProxy.t.sol @@ -40,10 +40,10 @@ contract L2FarmProxyTest is DssTest { function testConstructor() public { vm.expectEmit(true, true, true, true); emit Rely(address(this)); - L2FarmProxy g = new L2FarmProxy(farm); - assertEq(address(g.farm()), farm); - assertEq(address(g.rewardsToken()), address(rewardsToken)); - assertEq(g.wards(address(this)), 1); + L2FarmProxy p = new L2FarmProxy(farm); + assertEq(address(p.farm()), farm); + assertEq(address(p.rewardsToken()), address(rewardsToken)); + assertEq(p.wards(address(this)), 1); } function testAuth() public { From 8fb5adf7f2d61a0e84b115b8a66c834ba5674417 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Tue, 25 Jun 2024 22:31:59 +0300 Subject: [PATCH 24/57] Apply suggestions from code review Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> --- .gitignore | 2 +- foundry.toml | 2 +- script/Estimate.s.sol | 2 +- src/L2FarmProxy.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index deea2d0..85198aa 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ out/ docs/ # Dotenv file -.env \ No newline at end of file +.env diff --git a/foundry.toml b/foundry.toml index 7547d01..4e39c39 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,4 +11,4 @@ fs_permissions = [ mainnet = { key = "${ETHERSCAN_KEY}" } sepolia = { key = "${ETHERSCAN_KEY}", chain = 11155111 } arbitrum_one = { key = "${ARBISCAN_KEY}", chain = 42161, url = "https://api.arbiscan.io/api" } -arbitrum_one_sepolia = { key = "${ARBISCAN_KEY}", chain = 421614, url = "https://api-sepolia.arbiscan.io/api" } \ No newline at end of file +arbitrum_one_sepolia = { key = "${ARBISCAN_KEY}", chain = 421614, url = "https://api-sepolia.arbiscan.io/api" } diff --git a/script/Estimate.s.sol b/script/Estimate.s.sol index 7e6fc52..e5e2f41 100644 --- a/script/Estimate.s.sol +++ b/script/Estimate.s.sol @@ -92,4 +92,4 @@ contract Estimate is Script { console2.log("L1S:", l1s); console2.log("Recommended maxGas:", maxGas); } -} \ No newline at end of file +} diff --git a/src/L2FarmProxy.sol b/src/L2FarmProxy.sol index 103573f..1b6bc08 100644 --- a/src/L2FarmProxy.sol +++ b/src/L2FarmProxy.sol @@ -68,4 +68,4 @@ contract L2FarmProxy { rewardsToken.transfer(address(farm), reward); farm.notifyRewardAmount(reward); } -} \ No newline at end of file +} From 71829f9208f1dbf400721b2677922c162c52f90c Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 26 Jun 2024 19:42:04 +0300 Subject: [PATCH 25/57] Add Deploy.s.sol --- .env.example | 12 ++ .gitignore | 6 +- README.md | 4 +- deploy/FarmProxyDeploy.sol | 4 +- foundry.toml | 3 +- script/Deploy.s.sol | 141 ++++++++++++++++++++ script/Estimate.s.sol | 14 +- script/input/1/config.json | 26 +--- script/input/11155111/config.json | 16 +++ script/output/1/deployed-latest.json | 1 + script/output/11155111/deployed-latest.json | 8 ++ test/Integration.t.sol | 5 +- 12 files changed, 206 insertions(+), 34 deletions(-) create mode 100644 .env.example create mode 100644 script/Deploy.s.sol create mode 100644 script/input/11155111/config.json create mode 100644 script/output/1/deployed-latest.json create mode 100644 script/output/11155111/deployed-latest.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fe55e92 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +FOUNDRY_SCRIPT_DEPS=deployed +FOUNDRY_EXPORTS_OVERWRITE_LATEST=true +L1="sepolia" +L2="arbitrum_one_sepolia" +ETH_RPC_URL= +ARBITRUM_ONE_RPC_URL= +SEPOLIA_RPC_URL= +ARBITRUM_ONE_SEPOLIA_RPC_URL= +DEPLOYER= +PRIVATE_KEY=$(cat /path/to/pkey) +ETHERSCAN_KEY= +ARBISCAN_KEY= diff --git a/.gitignore b/.gitignore index 85198aa..87132a9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,12 @@ cache/ out/ # Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ +/broadcast # Docs docs/ # Dotenv file .env + +deployed-[0-9]*.json diff --git a/README.md b/README.md index 8722a27..00f9bc6 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# Endgame L2 Farms +TODO: Rename repo to arbitrum-farms + +# Arbitrum Farms diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol index ec94790..c73086b 100644 --- a/deploy/FarmProxyDeploy.sol +++ b/deploy/FarmProxyDeploy.sol @@ -26,12 +26,12 @@ library FarmProxyDeploy { function deployL1Proxy( address deployer, address owner, - address gem, + address rewardsToken, address l2Proxy, address feeRecipient, address l1Gateway ) internal returns (address l1Proxy) { - l1Proxy = address(new L1FarmProxy(gem, l2Proxy, feeRecipient, l1Gateway)); + l1Proxy = address(new L1FarmProxy(rewardsToken, l2Proxy, feeRecipient, l1Gateway)); ScriptTools.switchOwner(l1Proxy, deployer, owner); } diff --git a/foundry.toml b/foundry.toml index 4e39c39..96b91ae 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,8 @@ out = "out" libs = ["lib"] solc = "0.8.21" fs_permissions = [ - { access = "read", path = "./script/input/"} + { access = "read", path = "./script/input/"}, + { access = "read-write", path = "./script/output/"} ] [etherscan] diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..7272afa --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; + +import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; +import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; +import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; +import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; + +interface ChainLogLike { + function getAddress(bytes32) external view returns (address); +} + +interface L1GovernanceRelayLike { + function l2GovernanceRelay() external view returns (address); +} + +interface AuthLike { + function rely(address usr) external; +} + +contract Deploy is Script { + StdChains.Chain l1Chain; + StdChains.Chain l2Chain; + string config; + Domain l1Domain; + Domain l2Domain; + address deployer; + ChainLogLike chainlog; + address owner; + address l1GovRelay; + address l2GovRelay; + address l1Gateway; + address vest; + address stakingToken; + address l1RewardsToken; + address l2RewardsToken; + address farm; + address l2Spell; + address l2Proxy; + address l1Proxy; + address vestedRewardsDistribution; + + function run() external { + l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); + l1Domain = new Domain(config, l1Chain); + l2Domain = new Domain(config, l2Chain); + l1Domain.selectFork(); + + (,deployer, ) = vm.readCallers(); + chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); + l1GovRelay = chainlog.getAddress("ARBITRUM_GOV_RELAY"); + l2GovRelay = L1GovernanceRelayLike(payable(l1GovRelay)).l2GovernanceRelay(); + l1Gateway = chainlog.getAddress("ARBITRUM_TOKEN_BRIDGE"); + l1RewardsToken = l1Domain.readConfigAddress("rewardsToken"); + + if (keccak256(bytes(l1Chain.chainAlias)) == keccak256("mainnet")) { + owner = chainlog.getAddress("MCD_PAUSE_PROXY"); + vest = l1Domain.readConfigAddress("vest"); + } else { + owner = deployer; + vm.startBroadcast(); + vest = address(new DssVestMintableMock(l1RewardsToken)); + DssVestMintableMock(vest).file("cap", type(uint256).max); + AuthLike(l1RewardsToken).rely(address(vest)); + vm.stopBroadcast(); + } + + // L2 deployment + + l2Domain.selectFork(); + + stakingToken = l2Domain.readConfigAddress("stakingToken"); + l2RewardsToken = l2Domain.readConfigAddress("rewardsToken"); + StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ + owner: l2GovRelay, + stakingToken: stakingToken, + rewardsToken: l2RewardsToken + }); + + vm.startBroadcast(); + farm = StakingRewardsDeploy.deploy(farmParams); + l2Spell = FarmProxyDeploy.deployL2ProxySpell(); + l2Proxy = FarmProxyDeploy.deployL2Proxy(deployer, l2GovRelay, farm); + vm.stopBroadcast(); + + // L1 deployment + + l1Domain.selectFork(); + + vm.startBroadcast(); + l1Proxy = FarmProxyDeploy.deployL1Proxy( + deployer, + owner, + l1RewardsToken, + l2Proxy, + l2GovRelay, + l1Gateway + ); + VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ + deployer: deployer, + owner: owner, + vest: vest, + rewards: l1Proxy + }); + vestedRewardsDistribution = (VestedRewardsDistributionDeploy.deploy(distributionParams)); + vm.stopBroadcast(); + + // Export contract addresses + + ScriptTools.exportContract("deployed", "farm", farm); + ScriptTools.exportContract("deployed", "l2Spell", l2Spell); + ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); + ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); + ScriptTools.exportContract("deployed", "vest", vest); + ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); // TODO: fix etherscan verification + } +} diff --git a/script/Estimate.s.sol b/script/Estimate.s.sol index e5e2f41..3424e63 100644 --- a/script/Estimate.s.sol +++ b/script/Estimate.s.sol @@ -37,19 +37,21 @@ contract Estimate is Script { using stdJson for string; function run() external { - string memory config = ScriptTools.readInput("config"); // loads from FOUNDRY_SCRIPT_CONFIG - - Domain l1Domain = new Domain(config, getChain(string(vm.envOr("L1", string("mainnet"))))); - Domain l2Domain = new Domain(config, getChain(vm.envOr("L2", string("arbitrum_one")))); + StdChains.Chain memory l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + StdChains.Chain memory l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + string memory config = ScriptTools.loadConfig("config"); + Domain l1Domain = new Domain(config, l1Chain); + Domain l2Domain = new Domain(config, l2Chain); l1Domain.selectFork(); (, address deployer,) = vm.readCallers(); address l1Gateway = l1Domain.readConfigAddress("gateway"); - address l1Nst = l1Domain.readConfigAddress("nst"); + address l1Token = l1Domain.readConfigAddress("rewardsToken"); address l2Gateway = l2Domain.readConfigAddress("gateway"); bytes memory finalizeDepositCalldata = GatewayLike(l1Gateway).getOutboundCalldata({ - l1Token: l1Nst, + l1Token: l1Token, from: deployer, to: address(uint160(uint256(keccak256(abi.encode(deployer, block.timestamp))))), // a pseudo-random address used as "fresh" destination address, amount: uint128(uint256(keccak256(abi.encode(deployer)))), // very large random-looking number => costlier calldata diff --git a/script/input/1/config.json b/script/input/1/config.json index b8b2336..90ee7b8 100644 --- a/script/input/1/config.json +++ b/script/input/1/config.json @@ -1,29 +1,17 @@ { "domains": { "mainnet": { - "type": "root", - "rpc": "ETH_RPC_URL", - "chainlog": "0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F" + "chainlog": "0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F", + "gateway": "0x0000000000000000000000000000000000000000", + "rewardsToken": "0x0000000000000000000000000000000000000000", + "vest": "0x0000000000000000000000000000000000000000" }, "arbitrum_one": { - "type": "arbitrum", - "rpc": "ARB_RPC_URL", "inbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", - "arbSys": "0x0000000000000000000000000000000000000064" - }, - "sepolia": { - "type": "root", - "rpc": "SEPOLIA_RPC_URL", - "gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", - "nst": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84" - }, - "arbitrum_one_sepolia": { - "type": "arbitrum", - "rpc": "ARBITRUM_ONE_SEPOLIA_RPC_URL", - "inbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", "arbSys": "0x0000000000000000000000000000000000000064", - "gateway": "0x0CB73f1A5732fbAeFAB5646B422eCF15E12db95B", - "nst": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c" + "gateway": "0x0000000000000000000000000000000000000000", + "rewardsToken": "0x0000000000000000000000000000000000000000", + "stakingToken": "0x0000000000000000000000000000000000000000" } } } diff --git a/script/input/11155111/config.json b/script/input/11155111/config.json new file mode 100644 index 0000000..13b377a --- /dev/null +++ b/script/input/11155111/config.json @@ -0,0 +1,16 @@ +{ + "domains": { + "sepolia": { + "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", + "gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", + "rewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84" + }, + "arbitrum_one_sepolia": { + "inbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", + "arbSys": "0x0000000000000000000000000000000000000064", + "gateway": "0x0CB73f1A5732fbAeFAB5646B422eCF15E12db95B", + "rewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", + "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813" + } + } +} diff --git a/script/output/1/deployed-latest.json b/script/output/1/deployed-latest.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/script/output/1/deployed-latest.json @@ -0,0 +1 @@ +{} diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json new file mode 100644 index 0000000..bb2edbd --- /dev/null +++ b/script/output/11155111/deployed-latest.json @@ -0,0 +1,8 @@ +{ + "farm": "0xadAc60656DE3910eec1c8d9397DDD1Cf9c927E40", + "l1Proxy": "0xEc047AcF475B358133c86837F7f60b573C1438DC", + "l2Proxy": "0xC6Fa6Ba54A8E6D085eDeaa9DB6d82C531181B808", + "l2Spell": "0xE14d87Cf739B500ADA7270aB69d5D0384CF7C906", + "vest": "0x71F1C62d77Af5836B0B91756cF2c07d26F202854", + "vestedRewardsDistribution": "0xcD64e39CF7Ddc8E76fb84ec9fE2f6409e2846714" +} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 86650e9..c57cbb3 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -72,7 +72,7 @@ contract IntegrationTest is DssTest { vm.label(address(ESCROW), "ESCROW"); l2Domain = new ArbitrumDomain(config, getChain("arbitrum_one"), l1Domain); - address inbox = l2Domain.readConfigAddress("inbox"); + address inbox = address(l2Domain.inbox()); vm.label(inbox, "INBOX"); address l1Gateway_ = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 2); // foundry increments a global nonce across domains @@ -128,7 +128,8 @@ contract IntegrationTest is DssTest { } function setUp() public { - config = ScriptTools.readInput("config"); + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); l1Domain = new Domain(config, getChain("mainnet")); l1Domain.selectFork(); From 214a78adc989231cdb8943d90c331382e8b95fd5 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 26 Jun 2024 21:53:09 +0300 Subject: [PATCH 26/57] Add Init.s.sol --- README.md | 44 +++++++ lib/arbitrum-token-bridge | 2 +- script/Deploy.s.sol | 9 +- script/Init.s.sol | 122 ++++++++++++++++++++ script/input/1/config.json | 2 - script/input/11155111/config.json | 2 - script/output/11155111/deployed-latest.json | 11 +- 7 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 script/Init.s.sol diff --git a/README.md b/README.md index 00f9bc6..4cbf52d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ TODO: Rename repo to arbitrum-farms # Arbitrum Farms + +## Deployment + +### Declare env variables + +Add the required env variables listed in `.env.example` to your `.env` file, and run `source .env`. + +Make sure to set the `L1` and `L2` env variables according to your desired deployment environment. + +Mainnet deployment: + +``` +L1=mainnet +L2=arbitrum_one +``` + +Testnet deployment: + +``` +L1=sepolia +L2=arbitrum_one_sepolia +``` + +### Deploy the farm L1 & L2 proxies + +The deployment assumes that the [arbitrum-token-bridge](https://github.com/makerdao/arbitrum-token-bridge) has already been deployed and was properly initialized. + +Fill in the addresses of the L2 staking token and L1 and L2 rewards tokens in `script/input/{chainId}/config.json` under the `"stakingToken"` and `"rewardsToken"` keys. It is assumed that these tokens have been registered with the Arbitrum Token Bridge. + +Fill in the address of the mainnet DssVest contract in `script/input/1/config.json` under the `vest` key. It is assumed that the vesting contract was properly initialized. On testnet, a mock DssVest contract will automatically be deployed. + +The following command deploys the L1 and L2 farm proxies: + +``` +forge script script/Deploy.s.sol:Deploy --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --verify --multi --broadcast +``` + +### Initialize the farm L1 & L2 proxies + +On mainnet, the farm proxies should be initialized via the spell process. On testnet, the proxies initialization can be performed via the following command: + +``` +forge script script/Init.s.sol:Init --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast +``` diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index 2471056..7fd3718 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit 2471056b009a6ff8f4b6c36e161bb6ee8021de6d +Subproject commit 7fd3718504900fe4407adf7bc18f74be546da068 diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 7272afa..3cc9116 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -131,11 +131,18 @@ contract Deploy is Script { // Export contract addresses + ScriptTools.exportContract("deployed", "chainlog", address(chainlog)); ScriptTools.exportContract("deployed", "farm", farm); - ScriptTools.exportContract("deployed", "l2Spell", l2Spell); + ScriptTools.exportContract("deployed", "l2ProxySpell", l2Spell); ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); ScriptTools.exportContract("deployed", "vest", vest); ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); // TODO: fix etherscan verification + ScriptTools.exportContract("deployed", "l1GovRelay", l1GovRelay); + ScriptTools.exportContract("deployed", "l2GovRelay", l2GovRelay); + ScriptTools.exportContract("deployed", "l1RewardsToken", l1RewardsToken); + ScriptTools.exportContract("deployed", "l2RewardsToken", l2RewardsToken); + ScriptTools.exportContract("deployed", "stakingToken", stakingToken); + ScriptTools.exportContract("deployed", "l1Gateway", l1Gateway); } } diff --git a/script/Init.s.sol b/script/Init.s.sol new file mode 100644 index 0000000..4965680 --- /dev/null +++ b/script/Init.s.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; +import { MCD, DssInstance } from "dss-test/MCD.sol"; +import { FarmProxyInit, ProxiesConfig, MessageParams } from "deploy/FarmProxyInit.sol"; +import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; +import { RetryableTickets } from "arbitrum-token-bridge/script/utils/RetryableTickets.sol"; + +interface L2GovernanceRelayLike { + function relay(address, bytes calldata) external; +} + +contract Init is Script { + using stdJson for string; + + StdChains.Chain l1Chain; + StdChains.Chain l2Chain; + string config; + string deps; + Domain l1Domain; + Domain l2Domain; + + function run() external { + l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); + deps = ScriptTools.loadDependencies(); + l1Domain = new Domain(config, l1Chain); + l2Domain = new Domain(config, l2Chain); + l1Domain.selectFork(); + + DssInstance memory dss = MCD.loadFromChainlog(deps.readAddress(".chainlog")); + + address l1GovRelay = deps.readAddress(".l1GovRelay"); + address l2GovRelay = deps.readAddress(".l2GovRelay"); + RetryableTickets retryable = new RetryableTickets(l1Domain, l2Domain, l1GovRelay, l2GovRelay); + + address l2Proxy = deps.readAddress(".l2Proxy"); + address l2ProxySpell = deps.readAddress(".l2ProxySpell"); + address l2RewardsToken = deps.readAddress(".l2RewardsToken"); + address stakingToken = deps.readAddress(".stakingToken"); + address farm = deps.readAddress(".farm"); + uint256 l2MinReward = 1 ether; + uint256 rewardsDuration = 1 days; + + bytes memory initCalldata = abi.encodeCall(L2GovernanceRelayLike.relay, ( + l2ProxySpell, + abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + l2RewardsToken, + stakingToken, + farm, + l2MinReward, + rewardsDuration + )) + )); + MessageParams memory xchainMsg = MessageParams({ + maxGas: retryable.getMaxGas(initCalldata) * 150 / 100, + gasPriceBid: retryable.getGasPriceBid() * 200 / 100, + maxSubmissionCost: retryable.getSubmissionFee(initCalldata) * 250 / 100 + }); + ProxiesConfig memory cfg = ProxiesConfig({ + vest: deps.readAddress(".vest"), + vestTot: 100 ether, + vestBgn: block.timestamp, + vestTau: 100 days, + vestMgr: address(0), + vestedRewardsDistribution: deps.readAddress(".vestedRewardsDistribution"), + l1RewardsToken: deps.readAddress(".l1RewardsToken"), + l2RewardsToken: l2RewardsToken, + stakingToken: stakingToken, + feeRecipient: deps.readAddress(".l2GovRelay"), + l1Gateway: deps.readAddress(".l1Gateway"), + maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin + gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor + l1MinReward: 1 ether, + l2MinReward: l2MinReward, + farm: farm, + rewardsDuration: rewardsDuration, + xchainMsg: xchainMsg, + proxyChainlogKey: "FARM_PROXY_TKA_TKB_ARB", + distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB" + }); + + vm.startBroadcast(); + uint256 minGovRelayBal = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; + if (l1GovRelay.balance < minGovRelayBal) { + (bool success,) = l1GovRelay.call{value: minGovRelayBal - l1GovRelay.balance}(""); + require(success, "l1GovRelay topup failed"); + } + + FarmProxyInit.initProxies( + dss, + deps.readAddress(".l1Proxy"), + l2Proxy, + l2ProxySpell, + cfg + ); + vm.stopBroadcast(); + } +} diff --git a/script/input/1/config.json b/script/input/1/config.json index 90ee7b8..ede643d 100644 --- a/script/input/1/config.json +++ b/script/input/1/config.json @@ -2,14 +2,12 @@ "domains": { "mainnet": { "chainlog": "0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F", - "gateway": "0x0000000000000000000000000000000000000000", "rewardsToken": "0x0000000000000000000000000000000000000000", "vest": "0x0000000000000000000000000000000000000000" }, "arbitrum_one": { "inbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", "arbSys": "0x0000000000000000000000000000000000000064", - "gateway": "0x0000000000000000000000000000000000000000", "rewardsToken": "0x0000000000000000000000000000000000000000", "stakingToken": "0x0000000000000000000000000000000000000000" } diff --git a/script/input/11155111/config.json b/script/input/11155111/config.json index 13b377a..c105258 100644 --- a/script/input/11155111/config.json +++ b/script/input/11155111/config.json @@ -2,13 +2,11 @@ "domains": { "sepolia": { "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", - "gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", "rewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84" }, "arbitrum_one_sepolia": { "inbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", "arbSys": "0x0000000000000000000000000000000000000064", - "gateway": "0x0CB73f1A5732fbAeFAB5646B422eCF15E12db95B", "rewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813" } diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json index bb2edbd..0cb66d7 100644 --- a/script/output/11155111/deployed-latest.json +++ b/script/output/11155111/deployed-latest.json @@ -1,8 +1,15 @@ { + "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", "farm": "0xadAc60656DE3910eec1c8d9397DDD1Cf9c927E40", "l1Proxy": "0xEc047AcF475B358133c86837F7f60b573C1438DC", "l2Proxy": "0xC6Fa6Ba54A8E6D085eDeaa9DB6d82C531181B808", - "l2Spell": "0xE14d87Cf739B500ADA7270aB69d5D0384CF7C906", + "l2ProxySpell": "0xE14d87Cf739B500ADA7270aB69d5D0384CF7C906", "vest": "0x71F1C62d77Af5836B0B91756cF2c07d26F202854", - "vestedRewardsDistribution": "0xcD64e39CF7Ddc8E76fb84ec9fE2f6409e2846714" + "vestedRewardsDistribution": "0xcD64e39CF7Ddc8E76fb84ec9fE2f6409e2846714", + "l1GovRelay": "0x1Fc16121472E5990A112EC43266edf32E2a97fF8", + "l2GovRelay": "0xA87F8FFC547ca1613f0d22Ce288C39e1BBffEbf6", + "l1RewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84", + "l2RewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", + "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813", + "l1Gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4" } From 9e2b4610214d2a9c22640177b76137e0c8e13b80 Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 27 Jun 2024 12:51:38 +0300 Subject: [PATCH 27/57] Fix tests and apply correct alias to feeRecipient --- script/Deploy.s.sol | 4 +++- script/Init.s.sol | 14 ++++++++++---- script/output/11155111/deployed-latest.json | 18 +++++++++--------- src/L1FarmProxy.sol | 2 +- test/Integration.t.sol | 20 +++++++++++++------- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 3cc9116..ea53c81 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -22,6 +22,7 @@ import "forge-std/Script.sol"; import { ScriptTools } from "dss-test/ScriptTools.sol"; import { Domain } from "dss-test/domains/Domain.sol"; +import { AddressAliasHelper } from "lib/arbitrum-token-bridge/src/arbitrum/AddressAliasHelper.sol"; import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; @@ -112,12 +113,13 @@ contract Deploy is Script { l1Domain.selectFork(); vm.startBroadcast(); + address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out l1Proxy = FarmProxyDeploy.deployL1Proxy( deployer, owner, l1RewardsToken, l2Proxy, - l2GovRelay, + feeRecipient, l1Gateway ); VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ diff --git a/script/Init.s.sol b/script/Init.s.sol index 4965680..4db3cd3 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -25,6 +25,7 @@ import { MCD, DssInstance } from "dss-test/MCD.sol"; import { FarmProxyInit, ProxiesConfig, MessageParams } from "deploy/FarmProxyInit.sol"; import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; import { RetryableTickets } from "arbitrum-token-bridge/script/utils/RetryableTickets.sol"; +import { AddressAliasHelper } from "arbitrum-token-bridge/src/arbitrum/AddressAliasHelper.sol"; interface L2GovernanceRelayLike { function relay(address, bytes calldata) external; @@ -39,6 +40,10 @@ contract Init is Script { string deps; Domain l1Domain; Domain l2Domain; + DssInstance dss; + + address l1GovRelay; + address l2GovRelay; function run() external { l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); @@ -50,12 +55,13 @@ contract Init is Script { l2Domain = new Domain(config, l2Chain); l1Domain.selectFork(); - DssInstance memory dss = MCD.loadFromChainlog(deps.readAddress(".chainlog")); + dss = MCD.loadFromChainlog(deps.readAddress(".chainlog")); - address l1GovRelay = deps.readAddress(".l1GovRelay"); - address l2GovRelay = deps.readAddress(".l2GovRelay"); + l1GovRelay = deps.readAddress(".l1GovRelay"); + l2GovRelay = deps.readAddress(".l2GovRelay"); RetryableTickets retryable = new RetryableTickets(l1Domain, l2Domain, l1GovRelay, l2GovRelay); + address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out address l2Proxy = deps.readAddress(".l2Proxy"); address l2ProxySpell = deps.readAddress(".l2ProxySpell"); address l2RewardsToken = deps.readAddress(".l2RewardsToken"); @@ -90,7 +96,7 @@ contract Init is Script { l1RewardsToken: deps.readAddress(".l1RewardsToken"), l2RewardsToken: l2RewardsToken, stakingToken: stakingToken, - feeRecipient: deps.readAddress(".l2GovRelay"), + feeRecipient: feeRecipient, l1Gateway: deps.readAddress(".l1Gateway"), maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json index 0cb66d7..8d3ac28 100644 --- a/script/output/11155111/deployed-latest.json +++ b/script/output/11155111/deployed-latest.json @@ -1,15 +1,15 @@ { "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", - "farm": "0xadAc60656DE3910eec1c8d9397DDD1Cf9c927E40", - "l1Proxy": "0xEc047AcF475B358133c86837F7f60b573C1438DC", - "l2Proxy": "0xC6Fa6Ba54A8E6D085eDeaa9DB6d82C531181B808", - "l2ProxySpell": "0xE14d87Cf739B500ADA7270aB69d5D0384CF7C906", - "vest": "0x71F1C62d77Af5836B0B91756cF2c07d26F202854", - "vestedRewardsDistribution": "0xcD64e39CF7Ddc8E76fb84ec9fE2f6409e2846714", + "farm": "0x44dF8F18Cb35cC0c1BD88e05BA1FaC1f50B7f23b", + "l1Gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", "l1GovRelay": "0x1Fc16121472E5990A112EC43266edf32E2a97fF8", - "l2GovRelay": "0xA87F8FFC547ca1613f0d22Ce288C39e1BBffEbf6", + "l1Proxy": "0x2Da4C06E10C47c80b6Cf9EF4a0C644E9d3641137", "l1RewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84", + "l2GovRelay": "0xA87F8FFC547ca1613f0d22Ce288C39e1BBffEbf6", + "l2Proxy": "0x71F1C62d77Af5836B0B91756cF2c07d26F202854", + "l2ProxySpell": "0x9d88F8B0b3968979Fa1c3a3DB3B74dD01087873b", "l2RewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813", - "l1Gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4" -} + "vest": "0xEB80f18Df58955Aa512386701B73d4EFA089de3F", + "vestedRewardsDistribution": "0x2542CD28133B464378955a83aA730F781D958CfF" +} \ No newline at end of file diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 825a0fa..cfa2f13 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -46,7 +46,7 @@ contract L1FarmProxy { address public immutable rewardsToken; address public immutable l2Proxy; - address public immutable feeRecipient; + address public immutable feeRecipient; // L2 recipient of excess fee. Negative alias must be applied to it if the address contains code on L1 InboxLike public immutable inbox; L1TokenGatewayLike public immutable l1Gateway; diff --git a/test/Integration.t.sol b/test/Integration.t.sol index c57cbb3..09811bd 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -26,6 +26,7 @@ import { TokenGatewayDeploy } from "lib/arbitrum-token-bridge/deploy/TokenGatewa import { L2TokenGatewaySpell } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewaySpell.sol"; import { L2TokenGatewayInstance } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewayInstance.sol"; import { TokenGatewayInit, GatewaysConfig, MessageParams as GatewayMessageParams } from "lib/arbitrum-token-bridge/deploy/TokenGatewayInit.sol"; +import { AddressAliasHelper } from "lib/arbitrum-token-bridge/src/arbitrum/AddressAliasHelper.sol"; import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; @@ -44,6 +45,10 @@ interface L1RelayLike { function l2GovernanceRelay() external view returns (address); } +contract L1RouterMock { + function counterpartGateway() external view returns (address) {} +} + contract IntegrationTest is DssTest { string config; Domain l1Domain; @@ -53,6 +58,7 @@ contract IntegrationTest is DssTest { DssInstance dss; address PAUSE_PROXY; address ESCROW; + address L1_ROUTER; GemMock l1Token; address l1Gateway; L1FarmProxy l1Proxy; @@ -91,7 +97,7 @@ contract IntegrationTest is DssTest { deployer: address(this), owner: PAUSE_PROXY, l2Gateway: address(l2Gateway), - l1Router: address(0), + l1Router: L1_ROUTER, inbox: inbox, escrow: ESCROW }); @@ -114,7 +120,7 @@ contract IntegrationTest is DssTest { }); GatewaysConfig memory cfg = GatewaysConfig({ counterpartGateway: address(l2Gateway), - l1Router: address(0), + l1Router: L1_ROUTER, inbox: inbox, l1Tokens: l1Tokens, l2Tokens: l2Tokens, @@ -137,8 +143,7 @@ contract IntegrationTest is DssTest { dss = l1Domain.dss(); PAUSE_PROXY = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); L2_GOV_RELAY = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")).l2GovernanceRelay(); - vm.label(address(PAUSE_PROXY), "PAUSE_PROXY"); - vm.label(address(L2_GOV_RELAY), "L2_GOV_RELAY"); + L1_ROUTER = address(new L1RouterMock()); vm.startPrank(PAUSE_PROXY); l1Token = new GemMock(100 ether); @@ -167,12 +172,13 @@ contract IntegrationTest is DssTest { address l2Spell = FarmProxyDeploy.deployL2ProxySpell(); l1Domain.selectFork(); + address feeRecipient = AddressAliasHelper.undoL1ToL2Alias(L2_GOV_RELAY); // the address of L2_GOV_RELAY has code on L1 as well and so will be aliased, which we want to cancel out l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ deployer: address(this), owner: PAUSE_PROXY, - gem: address(l1Token), + rewardsToken: address(l1Token), l2Proxy: address(l2Proxy), - feeRecipient: L2_GOV_RELAY, + feeRecipient: feeRecipient, l1Gateway: l1Gateway }))); @@ -201,7 +207,7 @@ contract IntegrationTest is DssTest { l1RewardsToken: address(l1Token), l2RewardsToken: address(l2Token), stakingToken: stakingToken, - feeRecipient: L2_GOV_RELAY, + feeRecipient: feeRecipient, l1Gateway: l1Gateway, maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor From 6ba732877acdc7222aaafd444c255978fd7cdb46 Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 27 Jun 2024 13:29:57 +0300 Subject: [PATCH 28/57] Move feeRecipient aliasing to init lib --- deploy/FarmProxyDeploy.sol | 15 +++++++++- deploy/FarmProxyInit.sol | 13 +++++++-- deploy/utils/AddressAliasHelper.sol | 43 +++++++++++++++++++++++++++++ script/Deploy.s.sol | 4 +-- script/Init.s.sol | 3 -- test/Integration.t.sol | 5 +--- 6 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 deploy/utils/AddressAliasHelper.sol diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol index c73086b..65f9c43 100644 --- a/deploy/FarmProxyDeploy.sol +++ b/deploy/FarmProxyDeploy.sol @@ -21,16 +21,29 @@ import { ScriptTools } from "dss-test/ScriptTools.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; import { L1FarmProxy } from "src/L1FarmProxy.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; +import { AddressAliasHelper } from "./utils/AddressAliasHelper.sol"; + +interface ChainlogLike { + function getAddress(bytes32) external view returns (address); +} + +interface L1RelayLike { + function l2GovernanceRelay() external view returns (address); +} library FarmProxyDeploy { function deployL1Proxy( address deployer, address owner, + address chainlog, address rewardsToken, address l2Proxy, - address feeRecipient, address l1Gateway ) internal returns (address l1Proxy) { + // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out + address l2GovRelay = L1RelayLike(ChainlogLike(chainlog).getAddress("ARBITRUM_GOV_RELAY")).l2GovernanceRelay(); + address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; + l1Proxy = address(new L1FarmProxy(rewardsToken, l2Proxy, feeRecipient, l1Gateway)); ScriptTools.switchOwner(l1Proxy, deployer, owner); } diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index c06be52..4d28aee 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -18,6 +18,7 @@ pragma solidity >=0.8.0; import { DssInstance } from "dss-test/MCD.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; +import { AddressAliasHelper } from "./utils/AddressAliasHelper.sol"; interface DssVestLike { function gem() external view returns (address); @@ -41,6 +42,7 @@ interface L1FarmProxyLike { } interface L1RelayLike { + function l2GovernanceRelay() external view returns (address); function relay( address target, bytes calldata targetData, @@ -67,7 +69,6 @@ struct ProxiesConfig { address l1RewardsToken; address l2RewardsToken; address stakingToken; - address feeRecipient; address l1Gateway; uint256 maxGas; // For the L1 proxy uint256 gasPriceBid; // For the L1 proxy @@ -91,21 +92,28 @@ library FarmProxyInit { L1FarmProxyLike l1Proxy = L1FarmProxyLike(l1Proxy_); DssVestLike vest = DssVestLike(cfg.vest); VestedRewardsDistributionLike distribution = VestedRewardsDistributionLike(cfg.vestedRewardsDistribution); + L1RelayLike l1GovRelay = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")); // sanity checks + { + // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out + address l2GovRelay = l1GovRelay.l2GovernanceRelay(); + address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; + require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); - require(l1Proxy.feeRecipient() == cfg.feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.feeRecipient() == feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); + } // setup vest @@ -128,7 +136,6 @@ library FarmProxyInit { // setup L2 proxy - L1RelayLike l1GovRelay = L1RelayLike(dss.chainlog.getAddress("ARBITRUM_GOV_RELAY")); uint256 l1CallValue = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; // not strictly necessary (as the retryable ticket creation would otherwise fail) diff --git a/deploy/utils/AddressAliasHelper.sol b/deploy/utils/AddressAliasHelper.sol new file mode 100644 index 0000000..7bcdbb9 --- /dev/null +++ b/deploy/utils/AddressAliasHelper.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2019-2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pragma solidity ^0.8.0; + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index ea53c81..00eea5d 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -22,7 +22,6 @@ import "forge-std/Script.sol"; import { ScriptTools } from "dss-test/ScriptTools.sol"; import { Domain } from "dss-test/domains/Domain.sol"; -import { AddressAliasHelper } from "lib/arbitrum-token-bridge/src/arbitrum/AddressAliasHelper.sol"; import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; @@ -113,13 +112,12 @@ contract Deploy is Script { l1Domain.selectFork(); vm.startBroadcast(); - address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out l1Proxy = FarmProxyDeploy.deployL1Proxy( deployer, owner, + address(chainlog), l1RewardsToken, l2Proxy, - feeRecipient, l1Gateway ); VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ diff --git a/script/Init.s.sol b/script/Init.s.sol index 4db3cd3..e465e01 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -25,7 +25,6 @@ import { MCD, DssInstance } from "dss-test/MCD.sol"; import { FarmProxyInit, ProxiesConfig, MessageParams } from "deploy/FarmProxyInit.sol"; import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; import { RetryableTickets } from "arbitrum-token-bridge/script/utils/RetryableTickets.sol"; -import { AddressAliasHelper } from "arbitrum-token-bridge/src/arbitrum/AddressAliasHelper.sol"; interface L2GovernanceRelayLike { function relay(address, bytes calldata) external; @@ -61,7 +60,6 @@ contract Init is Script { l2GovRelay = deps.readAddress(".l2GovRelay"); RetryableTickets retryable = new RetryableTickets(l1Domain, l2Domain, l1GovRelay, l2GovRelay); - address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out address l2Proxy = deps.readAddress(".l2Proxy"); address l2ProxySpell = deps.readAddress(".l2ProxySpell"); address l2RewardsToken = deps.readAddress(".l2RewardsToken"); @@ -96,7 +94,6 @@ contract Init is Script { l1RewardsToken: deps.readAddress(".l1RewardsToken"), l2RewardsToken: l2RewardsToken, stakingToken: stakingToken, - feeRecipient: feeRecipient, l1Gateway: deps.readAddress(".l1Gateway"), maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 09811bd..2dacfbb 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -26,7 +26,6 @@ import { TokenGatewayDeploy } from "lib/arbitrum-token-bridge/deploy/TokenGatewa import { L2TokenGatewaySpell } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewaySpell.sol"; import { L2TokenGatewayInstance } from "lib/arbitrum-token-bridge/deploy/L2TokenGatewayInstance.sol"; import { TokenGatewayInit, GatewaysConfig, MessageParams as GatewayMessageParams } from "lib/arbitrum-token-bridge/deploy/TokenGatewayInit.sol"; -import { AddressAliasHelper } from "lib/arbitrum-token-bridge/src/arbitrum/AddressAliasHelper.sol"; import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; @@ -172,13 +171,12 @@ contract IntegrationTest is DssTest { address l2Spell = FarmProxyDeploy.deployL2ProxySpell(); l1Domain.selectFork(); - address feeRecipient = AddressAliasHelper.undoL1ToL2Alias(L2_GOV_RELAY); // the address of L2_GOV_RELAY has code on L1 as well and so will be aliased, which we want to cancel out l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ deployer: address(this), owner: PAUSE_PROXY, + chainlog: address(dss.chainlog), rewardsToken: address(l1Token), l2Proxy: address(l2Proxy), - feeRecipient: feeRecipient, l1Gateway: l1Gateway }))); @@ -207,7 +205,6 @@ contract IntegrationTest is DssTest { l1RewardsToken: address(l1Token), l2RewardsToken: address(l2Token), stakingToken: stakingToken, - feeRecipient: feeRecipient, l1Gateway: l1Gateway, maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor From 2229c3a8716e62cfbfd1c27b86badf5f489c5a8c Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 27 Jun 2024 16:52:41 +0300 Subject: [PATCH 29/57] Move dealiasing to L1FarmProxy --- deploy/FarmProxyDeploy.sol | 11 +------- deploy/FarmProxyInit.sol | 9 +----- deploy/utils/AddressAliasHelper.sol | 43 ----------------------------- script/Deploy.s.sol | 2 +- src/L1FarmProxy.sol | 21 +++++++++++++- test/Integration.t.sol | 2 +- 6 files changed, 24 insertions(+), 64 deletions(-) delete mode 100644 deploy/utils/AddressAliasHelper.sol diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol index 65f9c43..a9698ae 100644 --- a/deploy/FarmProxyDeploy.sol +++ b/deploy/FarmProxyDeploy.sol @@ -21,29 +21,20 @@ import { ScriptTools } from "dss-test/ScriptTools.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; import { L1FarmProxy } from "src/L1FarmProxy.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; -import { AddressAliasHelper } from "./utils/AddressAliasHelper.sol"; interface ChainlogLike { function getAddress(bytes32) external view returns (address); } -interface L1RelayLike { - function l2GovernanceRelay() external view returns (address); -} - library FarmProxyDeploy { function deployL1Proxy( address deployer, address owner, - address chainlog, + address feeRecipient, address rewardsToken, address l2Proxy, address l1Gateway ) internal returns (address l1Proxy) { - // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out - address l2GovRelay = L1RelayLike(ChainlogLike(chainlog).getAddress("ARBITRUM_GOV_RELAY")).l2GovernanceRelay(); - address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; - l1Proxy = address(new L1FarmProxy(rewardsToken, l2Proxy, feeRecipient, l1Gateway)); ScriptTools.switchOwner(l1Proxy, deployer, owner); } diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 4d28aee..5ba11a2 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -18,7 +18,6 @@ pragma solidity >=0.8.0; import { DssInstance } from "dss-test/MCD.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; -import { AddressAliasHelper } from "./utils/AddressAliasHelper.sol"; interface DssVestLike { function gem() external view returns (address); @@ -96,24 +95,18 @@ library FarmProxyInit { // sanity checks - { - // if the address of l2GovRelay has code on L1, it will be aliased, which we want to cancel out - address l2GovRelay = l1GovRelay.l2GovernanceRelay(); - address feeRecipient = (l2GovRelay.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(l2GovRelay) : l2GovRelay; - require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); - require(l1Proxy.feeRecipient() == feeRecipient, "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.feeRecipient() == l1GovRelay.l2GovernanceRelay(), "FarmProxyInit/fee-recipient-mismatch"); require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); - } // setup vest diff --git a/deploy/utils/AddressAliasHelper.sol b/deploy/utils/AddressAliasHelper.sol deleted file mode 100644 index 7bcdbb9..0000000 --- a/deploy/utils/AddressAliasHelper.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2019-2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -pragma solidity ^0.8.0; - -library AddressAliasHelper { - uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); - - /// @notice Utility function that converts the address in the L1 that submitted a tx to - /// the inbox to the msg.sender viewed in the L2 - /// @param l1Address the address in the L1 that triggered the tx to L2 - /// @return l2Address L2 address as viewed in msg.sender - function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { - unchecked { - l2Address = address(uint160(l1Address) + offset); - } - } - - /// @notice Utility function that converts the msg.sender viewed in the L2 to the - /// address in the L1 that submitted a tx to the inbox - /// @param l2Address L2 address as viewed in msg.sender - /// @return l1Address the address in the L1 that triggered the tx to L2 - function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { - unchecked { - l1Address = address(uint160(l2Address) - offset); - } - } -} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 00eea5d..f08d7d4 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -115,7 +115,7 @@ contract Deploy is Script { l1Proxy = FarmProxyDeploy.deployL1Proxy( deployer, owner, - address(chainlog), + l2GovRelay, l1RewardsToken, l2Proxy, l1Gateway diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index cfa2f13..3056a7c 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -38,6 +38,21 @@ interface InboxLike { function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) external view returns (uint256); } +// From https://github.com/OffchainLabs/nitro-contracts/blob/90037b996509312ef1addb3f9352457b8a99d6a6/src/libraries/AddressAliasHelper.sol +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} + contract L1FarmProxy { mapping (address => uint256) public wards; uint64 public maxGas; @@ -119,9 +134,13 @@ contract L1FarmProxy { require(reward > 0 && reward >= minReward_, "L1FarmProxy/reward-too-small"); (uint256 l1CallValue, uint256 maxSubmissionCost) = estimateDepositCost(0, maxGas_, gasPriceBid_); + + // If the address of feeRecipient has code on L1, it will be aliased by the Arbitrum Inbox, which we want to cancel out here + address refundTo = (feeRecipient.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(feeRecipient) : feeRecipient; + l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ l1Token: rewardsToken, - refundTo: feeRecipient, + refundTo: refundTo, to: l2Proxy, amount: reward, maxGas: maxGas_, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 2dacfbb..2ddb8f6 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -174,7 +174,7 @@ contract IntegrationTest is DssTest { l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ deployer: address(this), owner: PAUSE_PROXY, - chainlog: address(dss.chainlog), + feeRecipient: L2_GOV_RELAY, rewardsToken: address(l1Token), l2Proxy: address(l2Proxy), l1Gateway: l1Gateway From 2093296d86eda867d247fd03d1b69095fddaf9da Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 27 Jun 2024 18:30:17 +0300 Subject: [PATCH 30/57] Add deploy test scripts and fix nits --- README.md | 14 +++++ deploy/FarmProxyDeploy.sol | 6 +-- script/Deploy.s.sol | 2 +- script/Distribute.s.sol | 57 +++++++++++++++++++++ script/Forward.s.sol | 48 +++++++++++++++++ script/Init.s.sol | 4 +- script/output/11155111/deployed-latest.json | 12 ++--- test/Integration.t.sol | 2 +- 8 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 script/Distribute.s.sol create mode 100644 script/Forward.s.sol diff --git a/README.md b/README.md index 4cbf52d..5d2c960 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,17 @@ On mainnet, the farm proxies should be initialized via the spell process. On tes ``` forge script script/Init.s.sol:Init --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast ``` + +### Run a test distribution + +Run the following command to distribute the vested funds to the L1 proxy: + +``` +forge script script/Distribute.s.sol:Distribute --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast +``` + +Wait for the transaction to be relayed to L2, then run the following command to forward the bridged funds from the L2 proxy to the farm: + +``` +forge script script/Forward.s.sol:Forward --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast +``` diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol index a9698ae..c73086b 100644 --- a/deploy/FarmProxyDeploy.sol +++ b/deploy/FarmProxyDeploy.sol @@ -22,17 +22,13 @@ import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; import { L1FarmProxy } from "src/L1FarmProxy.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; -interface ChainlogLike { - function getAddress(bytes32) external view returns (address); -} - library FarmProxyDeploy { function deployL1Proxy( address deployer, address owner, - address feeRecipient, address rewardsToken, address l2Proxy, + address feeRecipient, address l1Gateway ) internal returns (address l1Proxy) { l1Proxy = address(new L1FarmProxy(rewardsToken, l2Proxy, feeRecipient, l1Gateway)); diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index f08d7d4..3cc9116 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -115,9 +115,9 @@ contract Deploy is Script { l1Proxy = FarmProxyDeploy.deployL1Proxy( deployer, owner, - l2GovRelay, l1RewardsToken, l2Proxy, + l2GovRelay, l1Gateway ); VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ diff --git a/script/Distribute.s.sol b/script/Distribute.s.sol new file mode 100644 index 0000000..c0b8721 --- /dev/null +++ b/script/Distribute.s.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; + +interface DistributionLike { + function distribute() external returns (uint256); +} + +interface L1ProxyLike { + function estimateDepositCost(uint256, uint256, uint256) external view returns (uint256, uint256); +} + +// Run vestedRewardsDistribution.distribute() to test deployement +contract Distribute is Script { + using stdJson for string; + + function run() external { + StdChains.Chain memory l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + string memory config = ScriptTools.loadConfig("config"); + string memory deps = ScriptTools.loadDependencies(); + Domain l1Domain = new Domain(config, l1Chain); + l1Domain.selectFork(); + + DistributionLike distribution = DistributionLike(deps.readAddress(".vestedRewardsDistribution")); + address l1Proxy = deps.readAddress(".l1Proxy"); + (uint256 l1CallValue,) = L1ProxyLike(l1Proxy).estimateDepositCost(2 * block.basefee, 0, 0); + + vm.startBroadcast(); + if (l1Proxy.balance < l1CallValue) { + (bool success,) = l1Proxy.call{value: l1CallValue - l1Proxy.balance}(""); + require(success, "l1Proxy topup failed"); + } + distribution.distribute(); + vm.stopBroadcast(); + } +} diff --git a/script/Forward.s.sol b/script/Forward.s.sol new file mode 100644 index 0000000..3d030d6 --- /dev/null +++ b/script/Forward.s.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; + +interface L2ProxyLike { + function forwardReward() external; +} + +// Run l2Proxy.forwardReward() to test deployement +contract Forward is Script { + using stdJson for string; + + function run() external { + StdChains.Chain memory l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + StdChains.Chain memory l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + string memory config = ScriptTools.loadConfig("config"); + string memory deps = ScriptTools.loadDependencies(); + Domain l2Domain = new Domain(config, l2Chain); + l2Domain.selectFork(); + + address l2Proxy = deps.readAddress(".l2Proxy"); + + vm.startBroadcast(); + L2ProxyLike(l2Proxy).forwardReward(); + vm.stopBroadcast(); + } +} diff --git a/script/Init.s.sol b/script/Init.s.sol index e465e01..3110968 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -65,7 +65,7 @@ contract Init is Script { address l2RewardsToken = deps.readAddress(".l2RewardsToken"); address stakingToken = deps.readAddress(".stakingToken"); address farm = deps.readAddress(".farm"); - uint256 l2MinReward = 1 ether; + uint256 l2MinReward = 1; // 1 wei uint256 rewardsDuration = 1 days; bytes memory initCalldata = abi.encodeCall(L2GovernanceRelayLike.relay, ( @@ -97,7 +97,7 @@ contract Init is Script { l1Gateway: deps.readAddress(".l1Gateway"), maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor - l1MinReward: 1 ether, + l1MinReward: 0, l2MinReward: l2MinReward, farm: farm, rewardsDuration: rewardsDuration, diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json index 8d3ac28..2c662d3 100644 --- a/script/output/11155111/deployed-latest.json +++ b/script/output/11155111/deployed-latest.json @@ -1,15 +1,15 @@ { "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", - "farm": "0x44dF8F18Cb35cC0c1BD88e05BA1FaC1f50B7f23b", + "farm": "0x2Da4C06E10C47c80b6Cf9EF4a0C644E9d3641137", "l1Gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", "l1GovRelay": "0x1Fc16121472E5990A112EC43266edf32E2a97fF8", - "l1Proxy": "0x2Da4C06E10C47c80b6Cf9EF4a0C644E9d3641137", + "l1Proxy": "0x0309B91a570a2e897d58eCc11cBDe47510300dBe", "l1RewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84", "l2GovRelay": "0xA87F8FFC547ca1613f0d22Ce288C39e1BBffEbf6", - "l2Proxy": "0x71F1C62d77Af5836B0B91756cF2c07d26F202854", - "l2ProxySpell": "0x9d88F8B0b3968979Fa1c3a3DB3B74dD01087873b", + "l2Proxy": "0x1cd9454a6Ec9e77963912aCe3F05f8859822825C", + "l2ProxySpell": "0x2542CD28133B464378955a83aA730F781D958CfF", "l2RewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813", - "vest": "0xEB80f18Df58955Aa512386701B73d4EFA089de3F", - "vestedRewardsDistribution": "0x2542CD28133B464378955a83aA730F781D958CfF" + "vest": "0x854b62E5467d388663066fAEEf526f2630d56755", + "vestedRewardsDistribution": "0x4c81067A84c87144efBc96B035658418a2BEd9dB" } \ No newline at end of file diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 2ddb8f6..05e1ce1 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -174,9 +174,9 @@ contract IntegrationTest is DssTest { l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ deployer: address(this), owner: PAUSE_PROXY, - feeRecipient: L2_GOV_RELAY, rewardsToken: address(l1Token), l2Proxy: address(l2Proxy), + feeRecipient: L2_GOV_RELAY, l1Gateway: l1Gateway }))); From 1441c87bf464081fd535070156fe39db7950d8b7 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:30:47 +0300 Subject: [PATCH 31/57] Update script/output/11155111/deployed-latest.json Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> --- script/output/11155111/deployed-latest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json index 2c662d3..edc4b9e 100644 --- a/script/output/11155111/deployed-latest.json +++ b/script/output/11155111/deployed-latest.json @@ -12,4 +12,4 @@ "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813", "vest": "0x854b62E5467d388663066fAEEf526f2630d56755", "vestedRewardsDistribution": "0x4c81067A84c87144efBc96B035658418a2BEd9dB" -} \ No newline at end of file +} From 820239689c987a2673ad0e6df34687abfe5ee8ab Mon Sep 17 00:00:00 2001 From: telome <> Date: Mon, 1 Jul 2024 16:36:12 +0300 Subject: [PATCH 32/57] Fix Estimate script --- README.md | 8 ++++++- script/Estimate.s.sol | 49 ++++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5d2c960..d2d7049 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,13 @@ forge script script/Deploy.s.sol:Deploy --sender $DEPLOYER --private-key $PRIVAT ### Initialize the farm L1 & L2 proxies -On mainnet, the farm proxies should be initialized via the spell process. On testnet, the proxies initialization can be performed via the following command: +On mainnet, the farm proxies should be initialized via the spell process. To determine an adequate value for the `maxGas` storage variable of `L1FarmProxy`, the `Estimate` script can be run: + +``` +forge script script/Estimate.s.sol:Estimate --sender $DEPLOYER --private-key $PRIVATE_KEY +``` + +On testnet, the proxies initialization can be performed via the following command: ``` forge script script/Init.s.sol:Init --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast diff --git a/script/Estimate.s.sol b/script/Estimate.s.sol index 3424e63..a05be6d 100644 --- a/script/Estimate.s.sol +++ b/script/Estimate.s.sol @@ -30,15 +30,24 @@ interface GatewayLike { uint256 amount, bytes memory data ) external pure returns (bytes memory); + function counterpartGateway() external view returns (address); +} + +interface ChainLogLike { + function getAddress(bytes32) external view returns (address); } // Estimate `maxGas` for L1FarmProxy contract Estimate is Script { using stdJson for string; + uint256 constant MAX_L1_BASE_FEE_ESTIMATE = 1 gwei; // worst-case estimate for l1BaseFeeEstimate (representing the blob base fee) returned from https://github.com/OffchainLabs/nitro-contracts/blob/90037b996509312ef1addb3f9352457b8a99d6a6/src/node-interface/NodeInterface.sol#L95 + bool constant USE_DAI_BRIDGE = true; // set to true if the new token gateway isn't yet initiated + function run() external { - StdChains.Chain memory l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); - StdChains.Chain memory l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + // Note: this script should not be run on testnet as l1BaseFeeEstimate can sometimes be 0 on sepolia + StdChains.Chain memory l1Chain = getChain(string("mainnet")); + StdChains.Chain memory l2Chain = getChain(string("arbitrum_one")); vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path string memory config = ScriptTools.loadConfig("config"); Domain l1Domain = new Domain(config, l1Chain); @@ -46,9 +55,17 @@ contract Estimate is Script { l1Domain.selectFork(); (, address deployer,) = vm.readCallers(); - address l1Gateway = l1Domain.readConfigAddress("gateway"); - address l1Token = l1Domain.readConfigAddress("rewardsToken"); - address l2Gateway = l2Domain.readConfigAddress("gateway"); + ChainLogLike chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); + address l1Gateway; + address l1Token; + if (USE_DAI_BRIDGE) { + l1Gateway = chainlog.getAddress("ARBITRUM_DAI_BRIDGE"); + l1Token = chainlog.getAddress("MCD_DAI"); + } else { + l1Gateway = chainlog.getAddress("ARBITRUM_TOKEN_BRIDGE"); + l1Token = l1Domain.readConfigAddress("rewardsToken"); + } + address l2Gateway = GatewayLike(l1Gateway).counterpartGateway(); bytes memory finalizeDepositCalldata = GatewayLike(l1Gateway).getOutboundCalldata({ l1Token: l1Token, @@ -76,22 +93,16 @@ contract Estimate is Script { "\"}]" ))); - (uint64 gasEstimate, uint64 gasEstimateForL1, uint256 l2BaseFee, uint256 l1BaseFeeEstimate) + (uint64 gasEstimate, uint64 gasEstimateForL1,, uint256 l1BaseFeeEstimate) = abi.decode(res, (uint64,uint64,uint256,uint256)); + uint256 l2ExecutionGas = gasEstimate - gasEstimateForL1; + uint256 maxExtraGasForDataPosting = gasEstimateForL1 * MAX_L1_BASE_FEE_ESTIMATE / l1BaseFeeEstimate; + uint256 maxGas = l2ExecutionGas + maxExtraGasForDataPosting; - uint256 l2g = gasEstimate - gasEstimateForL1; - uint256 l1p = 16 * l1BaseFeeEstimate; - uint256 l1s = gasEstimateForL1 * l2BaseFee / l1p; - - // maxGas is estimated based on the formula in https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#breaking-down-the-formula - // where we use: - // * (L1P)_max = 16 * 100 gwei - // * (P)_min = 0.01 gwei - uint256 maxGas = l2g + (16 * 100 gwei * l1s) / 0.01 gwei; - - console2.log("L2G:", l2g); - console2.log("L1S:", l1s); - console2.log("Recommended maxGas:", maxGas); + console2.log(" L2 Execution Gas:", l2ExecutionGas); + console2.log("Cur Data Posting Gas:", gasEstimateForL1); + console2.log("Max Data Posting Gas:", maxExtraGasForDataPosting); + console2.log(" Recommended maxGas:", maxGas); } } From 7777054606f0a40605513f73bb5ce7e3b268c1b5 Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 2 Jul 2024 18:59:31 +0300 Subject: [PATCH 33/57] Use minThreshold --- README.md | 2 -- deploy/FarmProxyInit.sol | 29 ++++++++++----------- deploy/L2FarmProxySpell.sol | 4 +-- lib/arbitrum-token-bridge | 2 +- script/Deploy.s.sol | 2 +- script/Init.s.sol | 8 +++--- script/output/11155111/deployed-latest.json | 12 ++++----- src/L1FarmProxy.sol | 12 ++++----- src/L2FarmProxy.sol | 6 ++--- test/Integration.t.sol | 16 +++++++----- test/L1FarmProxy.t.sol | 17 +++++------- test/L2FarmProxy.t.sol | 13 ++++----- 12 files changed, 60 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index d2d7049..9a0a4de 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -TODO: Rename repo to arbitrum-farms - # Arbitrum Farms ## Deployment diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 5ba11a2..ddbcd49 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -69,15 +69,15 @@ struct ProxiesConfig { address l2RewardsToken; address stakingToken; address l1Gateway; - uint256 maxGas; // For the L1 proxy - uint256 gasPriceBid; // For the L1 proxy - uint256 l1MinReward; // For the L1 proxy - uint256 l2MinReward; // For the L2 proxy - address farm; // The L2 farm - uint256 rewardsDuration; // For the L2 farm - MessageParams xchainMsg; // For the xchain message executing the L2 spell - bytes32 proxyChainlogKey; // Chainlog key for the L1 proxy - bytes32 distrChainlogKey; // Chainlog key for vestedRewardsDistribution + uint256 maxGas; // For the L1 proxy + uint256 gasPriceBid; // For the L1 proxy + uint256 l1RewardThreshold; // For the L1 proxy + uint256 l2RewardThreshold; // For the L2 proxy + address farm; // The L2 farm + uint256 rewardsDuration; // For the L2 farm + MessageParams xchainMsg; // For the xchain message executing the L2 spell + bytes32 proxyChainlogKey; // Chainlog key for the L1 proxy + bytes32 distrChainlogKey; // Chainlog key for vestedRewardsDistribution } library FarmProxyInit { @@ -105,8 +105,7 @@ library FarmProxyInit { require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); - require(cfg.l1MinReward <= type(uint128).max, "FarmProxyInit/l1-min-reward-out-of-bounds"); - require(cfg.l2MinReward > 0, "FarmProxyInit/l2-min-reward-out-of-bounds"); + require(cfg.l1RewardThreshold <= type(uint128).max, "FarmProxyInit/l1-reward-threshold-out-of-bounds"); // setup vest @@ -123,9 +122,9 @@ library FarmProxyInit { // setup L1 proxy - l1Proxy.file("maxGas", cfg.maxGas); - l1Proxy.file("gasPriceBid", cfg.gasPriceBid); - l1Proxy.file("minReward", cfg.l1MinReward); + l1Proxy.file("maxGas", cfg.maxGas); + l1Proxy.file("gasPriceBid", cfg.gasPriceBid); + l1Proxy.file("rewardThreshold", cfg.l1RewardThreshold); // setup L2 proxy @@ -142,7 +141,7 @@ library FarmProxyInit { cfg.l2RewardsToken, cfg.stakingToken, cfg.farm, - cfg.l2MinReward, + cfg.l2RewardThreshold, cfg.rewardsDuration )), l1CallValue: l1CallValue, diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index 92f8506..325bc81 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -51,7 +51,7 @@ contract L2FarmProxySpell { address rewardsToken, address stakingToken, address farm, - uint256 minReward, + uint256 rewardThreshold, uint256 rewardsDuration ) external { // sanity checks @@ -61,7 +61,7 @@ contract L2FarmProxySpell { require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token"); - L2FarmProxyLike(l2Proxy).file("minReward", minReward); + L2FarmProxyLike(l2Proxy).file("rewardThreshold", rewardThreshold); FarmLike(farm).setRewardsDistribution(l2Proxy); FarmLike(farm).setRewardsDuration(rewardsDuration); diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index 7fd3718..6248966 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit 7fd3718504900fe4407adf7bc18f74be546da068 +Subproject commit 6248966bd8dac6261fb296f9743a0b9432cfec71 diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 3cc9116..5a5e90e 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -137,7 +137,7 @@ contract Deploy is Script { ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); ScriptTools.exportContract("deployed", "vest", vest); - ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); // TODO: fix etherscan verification + ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); ScriptTools.exportContract("deployed", "l1GovRelay", l1GovRelay); ScriptTools.exportContract("deployed", "l2GovRelay", l2GovRelay); ScriptTools.exportContract("deployed", "l1RewardsToken", l1RewardsToken); diff --git a/script/Init.s.sol b/script/Init.s.sol index 3110968..7b9fa47 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -65,7 +65,7 @@ contract Init is Script { address l2RewardsToken = deps.readAddress(".l2RewardsToken"); address stakingToken = deps.readAddress(".stakingToken"); address farm = deps.readAddress(".farm"); - uint256 l2MinReward = 1; // 1 wei + uint256 l2RewardThreshold = 0; uint256 rewardsDuration = 1 days; bytes memory initCalldata = abi.encodeCall(L2GovernanceRelayLike.relay, ( @@ -75,7 +75,7 @@ contract Init is Script { l2RewardsToken, stakingToken, farm, - l2MinReward, + l2RewardThreshold, rewardsDuration )) )); @@ -97,8 +97,8 @@ contract Init is Script { l1Gateway: deps.readAddress(".l1Gateway"), maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor - l1MinReward: 0, - l2MinReward: l2MinReward, + l1RewardThreshold: 0, + l2RewardThreshold: l2RewardThreshold, farm: farm, rewardsDuration: rewardsDuration, xchainMsg: xchainMsg, diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json index edc4b9e..7d0e506 100644 --- a/script/output/11155111/deployed-latest.json +++ b/script/output/11155111/deployed-latest.json @@ -1,15 +1,15 @@ { "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", - "farm": "0x2Da4C06E10C47c80b6Cf9EF4a0C644E9d3641137", + "farm": "0xde9763F97166B1c08cf3979675024cfB8ba0555D", "l1Gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", "l1GovRelay": "0x1Fc16121472E5990A112EC43266edf32E2a97fF8", - "l1Proxy": "0x0309B91a570a2e897d58eCc11cBDe47510300dBe", + "l1Proxy": "0x7fFAF728d086C357D37A44CA065251a2aCd758FD", "l1RewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84", "l2GovRelay": "0xA87F8FFC547ca1613f0d22Ce288C39e1BBffEbf6", - "l2Proxy": "0x1cd9454a6Ec9e77963912aCe3F05f8859822825C", - "l2ProxySpell": "0x2542CD28133B464378955a83aA730F781D958CfF", + "l2Proxy": "0x90C24Dd59CB87A4348a66D16E548959Cf5076ACC", + "l2ProxySpell": "0x135C3AB67098aad36B6a2cbfa15C50f1603a2DB0", "l2RewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813", - "vest": "0x854b62E5467d388663066fAEEf526f2630d56755", - "vestedRewardsDistribution": "0x4c81067A84c87144efBc96B035658418a2BEd9dB" + "vest": "0xE9035355498Ce947e67E6958A6A9409eBd60C8B0", + "vestedRewardsDistribution": "0x6e4ee3Bc399E632c8a5bCdB65BbD88D5463857a5" } diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 3056a7c..74c2955 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -57,7 +57,7 @@ contract L1FarmProxy { mapping (address => uint256) public wards; uint64 public maxGas; uint64 public gasPriceBid; - uint128 public minReward; + uint128 public rewardThreshold; address public immutable rewardsToken; address public immutable l2Proxy; @@ -93,9 +93,9 @@ contract L1FarmProxy { // @notice Validation of the `data` boundaries is outside the scope of this // contract and is assumed to be carried out in the corresponding spell process function file(bytes32 what, uint256 data) external auth { - if (what == "maxGas") maxGas = uint64(data); - else if (what == "gasPriceBid") gasPriceBid = uint64(data); - else if (what == "minReward") minReward = uint128(data); + if (what == "maxGas") maxGas = uint64(data); + else if (what == "gasPriceBid") gasPriceBid = uint64(data); + else if (what == "rewardThreshold") rewardThreshold = uint128(data); else revert("L1FarmProxy/file-unrecognized-param"); emit File(what, data); } @@ -129,9 +129,9 @@ contract L1FarmProxy { // This is mitigated by incorporating large enough safety factors in maxGas and gasPriceBid. // Note that in any case a failed auto-redeem can be permissionlessly retried for 7 days function notifyRewardAmount(uint256 reward) external { - (uint256 maxGas_, uint256 gasPriceBid_, uint256 minReward_) = (maxGas, gasPriceBid, minReward); + (uint256 maxGas_, uint256 gasPriceBid_, uint256 rewardThreshold_) = (maxGas, gasPriceBid, rewardThreshold); - require(reward > 0 && reward >= minReward_, "L1FarmProxy/reward-too-small"); + require(reward > rewardThreshold_, "L1FarmProxy/reward-too-small"); (uint256 l1CallValue, uint256 maxSubmissionCost) = estimateDepositCost(0, maxGas_, gasPriceBid_); diff --git a/src/L2FarmProxy.sol b/src/L2FarmProxy.sol index 1b6bc08..71221c6 100644 --- a/src/L2FarmProxy.sol +++ b/src/L2FarmProxy.sol @@ -29,7 +29,7 @@ interface GemLike { contract L2FarmProxy { mapping (address => uint256) public wards; - uint256 public minReward; + uint256 public rewardThreshold; GemLike public immutable rewardsToken; FarmLike public immutable farm; @@ -55,7 +55,7 @@ contract L2FarmProxy { function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } function file(bytes32 what, uint256 data) external auth { - if (what == "minReward") minReward = data; + if (what == "rewardThreshold") rewardThreshold = data; else revert("L2FarmProxy/file-unrecognized-param"); emit File(what, data); } @@ -64,7 +64,7 @@ contract L2FarmProxy { // calling this function too frequently in an attempt to reduce the rewardRate of the farm function forwardReward() external { uint256 reward = rewardsToken.balanceOf(address(this)); - require(reward >= minReward, "L2FarmProxy/reward-too-small"); + require(reward > rewardThreshold, "L2FarmProxy/reward-too-small"); rewardsToken.transfer(address(farm), reward); farm.notifyRewardAmount(reward); } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 05e1ce1..589074b 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -208,8 +208,8 @@ contract IntegrationTest is DssTest { l1Gateway: l1Gateway, maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor - l1MinReward: 0, - l2MinReward: 1 ether, + l1RewardThreshold: 1 ether, + l2RewardThreshold: 1 ether, farm: address(farm), rewardsDuration: 1 days, xchainMsg: xchainMsg, @@ -231,26 +231,28 @@ contract IntegrationTest is DssTest { assertEq(vest.res(vestId), 1); assertEq(l1Proxy.maxGas(), cfg.maxGas); assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); - assertEq(l1Proxy.minReward(), cfg.l1MinReward); + assertEq(l1Proxy.rewardThreshold(), cfg.l1RewardThreshold); assertEq(dss.chainlog.getAddress("FARM_PROXY_TKA_TKB_ARB"), address(l1Proxy)); assertEq(dss.chainlog.getAddress("REWARDS_DISTRIBUTION_TKA_TKB_ARB"), cfg.vestedRewardsDistribution); l2Domain.relayFromHost(true); // test L2 side of initProxies - assertEq(l2Proxy.minReward(), cfg.l2MinReward); + assertEq(l2Proxy.rewardThreshold(), cfg.l2RewardThreshold); assertEq(farm.rewardsDistribution(), address(l2Proxy)); assertEq(farm.rewardsDuration(), cfg.rewardsDuration); } function testDistribution() public { l2Domain.selectFork(); - uint256 minReward = l2Proxy.minReward(); + uint256 l2Th = l2Proxy.rewardThreshold(); l1Domain.selectFork(); - vm.warp(vest.bgn(vestId) + minReward * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId)); + uint256 l1Th = l1Proxy.rewardThreshold(); + uint256 maxThreshold = l2Th > l1Th ? l2Th : l1Th; + vm.warp(vest.bgn(vestId) + maxThreshold * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId) + 1); uint256 amount = vest.unpaid(vestId); - assertGe(amount, minReward); + assertGt(amount, maxThreshold); assertEq(l1Token.balanceOf(ESCROW), 0); vestedRewardsDistribution.distribute(); diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index 9f4564d..9aa0072 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -60,7 +60,7 @@ contract L1FarmProxyTest is DssTest { } function testFile() public { - checkFileUint(address(l1Proxy), "L1FarmProxy", ["maxGas", "gasPriceBid", "minReward"]); + checkFileUint(address(l1Proxy), "L1FarmProxy", ["maxGas", "gasPriceBid", "rewardThreshold"]); } function testAuthModifiers() public virtual { @@ -88,25 +88,22 @@ contract L1FarmProxyTest is DssTest { } function testNotifyRewardAmount() public { - vm.expectRevert("L1FarmProxy/reward-too-small"); - l1Proxy.notifyRewardAmount(0); - - l1Proxy.file("minReward", 1000 ether); + l1Proxy.file("rewardThreshold", 100 ether); vm.expectRevert("L1FarmProxy/reward-too-small"); - l1Proxy.notifyRewardAmount(500 ether); + l1Proxy.notifyRewardAmount(100 ether); (bool success,) = address(l1Proxy).call{value: 1 ether}(""); assertTrue(success); - rewardsToken.transfer(address(l1Proxy), 1000 ether); + rewardsToken.transfer(address(l1Proxy), 101 ether); assertEq(rewardsToken.balanceOf(escrow), 0); - assertEq(rewardsToken.balanceOf(address(l1Proxy)), 1000 ether); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), 101 ether); uint256 ethBefore = address(l1Proxy).balance; (uint256 l1CallValue,) = l1Proxy.estimateDepositCost(0, 0, 0); - l1Proxy.notifyRewardAmount(1000 ether); + l1Proxy.notifyRewardAmount(101 ether); - assertEq(rewardsToken.balanceOf(escrow), 1000 ether); + assertEq(rewardsToken.balanceOf(escrow), 101 ether); assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0); assertEq(address(l1Proxy).balance, ethBefore - l1CallValue); } diff --git a/test/L2FarmProxy.t.sol b/test/L2FarmProxy.t.sol index 7039c9d..5bb7b7f 100644 --- a/test/L2FarmProxy.t.sol +++ b/test/L2FarmProxy.t.sol @@ -51,24 +51,25 @@ contract L2FarmProxyTest is DssTest { } function testFile() public { - checkFileUint(address(l2Proxy), "L2FarmProxy", ["minReward"]); + checkFileUint(address(l2Proxy), "L2FarmProxy", ["rewardThreshold"]); } function testForwardReward() public { - l2Proxy.file("minReward", 1000 ether); + l2Proxy.file("rewardThreshold", 100 ether); + rewardsToken.transfer(address(l2Proxy), 100 ether); vm.expectRevert("L2FarmProxy/reward-too-small"); l2Proxy.forwardReward(); - rewardsToken.transfer(address(l2Proxy), 10_000 ether); + rewardsToken.transfer(address(l2Proxy), 1 ether); assertEq(rewardsToken.balanceOf(farm), 0); - assertEq(rewardsToken.balanceOf(address(l2Proxy)), 10_000 ether); + assertEq(rewardsToken.balanceOf(address(l2Proxy)), 101 ether); vm.expectEmit(true, true, true, true); - emit RewardAdded(10_000 ether); + emit RewardAdded(101 ether); l2Proxy.forwardReward(); - assertEq(rewardsToken.balanceOf(farm), 10_000 ether); + assertEq(rewardsToken.balanceOf(farm), 101 ether); assertEq(rewardsToken.balanceOf(address(l2Proxy)), 0); } } From 84680b885431c1c370e997d99bfbbc7d9f0c5b89 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 5 Jul 2024 15:07:41 +0300 Subject: [PATCH 34/57] emit RewardAdded from L1 proxy --- src/L1FarmProxy.sol | 3 +++ test/L1FarmProxy.t.sol | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 74c2955..8945a97 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -68,6 +68,7 @@ contract L1FarmProxy { event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, uint256 data); + event RewardAdded(uint256 reward); constructor(address _rewardsToken, address _l2Proxy, address _feeRecipient, address _l1Gateway) { rewardsToken = _rewardsToken; @@ -147,5 +148,7 @@ contract L1FarmProxy { gasPriceBid: gasPriceBid_, data: abi.encode(maxSubmissionCost, bytes("")) }); + + emit RewardAdded(reward); } } diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index 9aa0072..3f7f96a 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -34,6 +34,8 @@ contract L1FarmProxyTest is DssTest { address l2Proxy = address(0x222); address feeRecipient = address(0xfee); + event RewardAdded(uint256 rewards); + function setUp() public { inbox = address(new InboxMock()); gateway = address(new L1TokenGatewayMock(inbox, escrow)); @@ -101,6 +103,8 @@ contract L1FarmProxyTest is DssTest { uint256 ethBefore = address(l1Proxy).balance; (uint256 l1CallValue,) = l1Proxy.estimateDepositCost(0, 0, 0); + vm.expectEmit(true, true, true, true); + emit RewardAdded(101 ether); l1Proxy.notifyRewardAmount(101 ether); assertEq(rewardsToken.balanceOf(escrow), 101 ether); From 1376638a80b6ed3ff405b904201e4f5f2469b90e Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 9 Jul 2024 13:34:48 +0300 Subject: [PATCH 35/57] Complete README --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 9a0a4de..cb6b880 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,32 @@ # Arbitrum Farms +## Overview + +This repository implements a mechanism to distribute rewards vested in a [DssVest](https://github.com/makerdao/dss-vest) contract on L1 to users staking tokens in a [StakingRewards](https://github.com/makerdao/endgame-toolkit/blob/master/src/synthetix/StakingRewards.sol) farm on Arbitrum. It uses the [Arbitrum Token Bridge](https://github.com/makerdao/arbitrum-token-bridge) to transfer the rewards from L1 to L2. + +## Contracts + +- `L1FarmProxy.sol` - Proxy to the farm on the L1 side. Receives the token reward (expected to come from a [`VestedRewardDistribution`](https://github.com/makerdao/endgame-toolkit/blob/master/src/VestedRewardsDistribution.sol) contract) and transfers it cross-chain to the `L2FarmProxy`. An instance of `L1FarmProxy` must be deployed for each supported pair of staking and rewards token. +- `L2FarmProxy.sol` - Proxy to the farm on the L2 side. Receives the token reward (expected to be bridged from the `L1FarmProxy`) and forwards it to the [StakingRewards](https://github.com/makerdao/endgame-toolkit/blob/master/src/synthetix/StakingRewards.sol) farm where it gets distributed to stakers. An instance of `L2FarmProxy` must be deployed for each supported pair of staking and rewards token. + +### External dependencies + +- The L2 staking tokens and the L1 and L2 rewards tokens are not provided as part of this repository. It is assumed that only simple, regular ERC20 tokens will be used. In particular, the supported tokens are assumed to revert on failure (instead of returning false) and do not execute any hook on transfer. +- [`DssVest`](https://github.com/makerdao/dss-vest) is used to vest the rewards token on L1. +- [`VestedRewardDistribution`](https://github.com/makerdao/endgame-toolkit/blob/master/src/VestedRewardsDistribution.sol) is used to vest the rewards tokens from `DssVest`, transfer them to the `L1FarmProxy` and trigger the bridging of the tokens. +- The [Arbitrum Token Bridge](https://github.com/makerdao/arbitrum-token-bridge) is used to bridge the tokens from L1 to L2. +- The [escrow contract](https://etherscan.io/address/0xA10c7CE4b876998858b1a9E12b10092229539400#code) is used by the Arbitrum Token Bridge to hold the bridged tokens on L1. +- [`StakingRewards`](https://github.com/makerdao/endgame-toolkit/blob/master/src/synthetix/StakingRewards.sol) is used to distribute the bridged rewards to stakers on L2. +- The [`L1GovernanceRelay`](https://etherscan.io/address/0x9ba25c289e351779E0D481Ba37489317c34A899d#code) & [`L2GovernanceRelay`](https://arbiscan.io/address/0x10E6593CDda8c58a1d0f14C5164B376352a55f2F#code) allow governance to exert admin control over the deployed L2 contracts. These contracts have been previously deployed to control the Arbitrum Dai Bridge. + +## Expected flow + +- It is expected that the ether balance of the `L1FarmProxy` is continuously monitored and topped up as needed to ensure the successful operation of the proxy. +- Once the vested amount of rewards tokens exceeds `L1FarmProxy.rewardThreshold`, a keeper calls `VestedRewardDistribution.distribute()` to vest the rewards and have them bridged to L2. +- Once the bridged amount of rewards tokens exceeds `L2FarmProxy.rewardThreshold`, anyone (e.g. a keeper or an L2 staker) can call `L2FarmProxy.forwardReward()` to distribute the rewards to the L2 farm. + +Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the frequency of cross-chain transfers (thereby also reducing the amount of ether that needs to be provisionned into the `L1FarmProxy`). `L2FarmProxy.rewardThreshold` must also be sufficiently large to limit the reduction of the farm's rate of rewards distribution. + ## Deployment ### Declare env variables From a8c1570339a50b2f6877db08cee28cda8d4bb8d1 Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 9 Jul 2024 20:56:30 +0300 Subject: [PATCH 36/57] Remove vestMgr --- deploy/FarmProxyInit.sol | 3 +-- script/Init.s.sol | 1 - test/Integration.t.sol | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index ddbcd49..5b88d3e 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -63,7 +63,6 @@ struct ProxiesConfig { uint256 vestTot; uint256 vestBgn; uint256 vestTau; - address vestMgr; address vestedRewardsDistribution; address l1RewardsToken; address l2RewardsToken; @@ -115,7 +114,7 @@ library FarmProxyInit { _bgn: cfg.vestBgn, _tau: cfg.vestTau, _eta: 0, - _mgr: cfg.vestMgr + _mgr: address(0) }); vest.restrict(vestId); distribution.file("vestId", vestId); diff --git a/script/Init.s.sol b/script/Init.s.sol index 7b9fa47..6532ad0 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -89,7 +89,6 @@ contract Init is Script { vestTot: 100 ether, vestBgn: block.timestamp, vestTau: 100 days, - vestMgr: address(0), vestedRewardsDistribution: deps.readAddress(".vestedRewardsDistribution"), l1RewardsToken: deps.readAddress(".l1RewardsToken"), l2RewardsToken: l2RewardsToken, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 589074b..eb43d7b 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -200,7 +200,6 @@ contract IntegrationTest is DssTest { vestTot: 100 * 1e18, vestBgn: block.timestamp, vestTau: 100 days, - vestMgr: address(0), vestedRewardsDistribution: address(vestedRewardsDistribution), l1RewardsToken: address(l1Token), l2RewardsToken: address(l2Token), @@ -227,7 +226,6 @@ contract IntegrationTest is DssTest { assertEq(vest.bgn(vestId), cfg.vestBgn); assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); assertEq(vest.clf(vestId), cfg.vestBgn); - assertEq(vest.mgr(vestId), cfg.vestMgr); assertEq(vest.res(vestId), 1); assertEq(l1Proxy.maxGas(), cfg.maxGas); assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); From d356c1d8e12b3d8da11edca86860ba23012c49ab Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 9 Jul 2024 20:59:00 +0300 Subject: [PATCH 37/57] Update arbitrum-token-bridge --- lib/arbitrum-token-bridge | 2 +- test/Integration.t.sol | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index 6248966..c3c60c2 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit 6248966bd8dac6261fb296f9743a0b9432cfec71 +Subproject commit c3c60c2fcd870d48d6886c187c7cabb38c22ab76 diff --git a/test/Integration.t.sol b/test/Integration.t.sol index eb43d7b..a535d37 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -118,7 +118,6 @@ contract IntegrationTest is DssTest { maxSubmissionCost: 0.01 ether }); GatewaysConfig memory cfg = GatewaysConfig({ - counterpartGateway: address(l2Gateway), l1Router: L1_ROUTER, inbox: inbox, l1Tokens: l1Tokens, From e36873ed2c74ef8295033554ae89b663c13fb4a8 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 11:07:42 +0300 Subject: [PATCH 38/57] Check vest mgr is still 0 after spell --- test/Integration.t.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/Integration.t.sol b/test/Integration.t.sol index a535d37..6e92403 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -221,10 +221,11 @@ contract IntegrationTest is DssTest { // test L1 side of initProxies vestId = vestedRewardsDistribution.vestId(); assertEq(vest.usr(vestId), cfg.vestedRewardsDistribution); - assertEq(vest.tot(vestId), cfg.vestTot); assertEq(vest.bgn(vestId), cfg.vestBgn); - assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); assertEq(vest.clf(vestId), cfg.vestBgn); + assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau); + assertEq(vest.tot(vestId), cfg.vestTot); + assertEq(vest.mgr(vestId), address(0)); assertEq(vest.res(vestId), 1); assertEq(l1Proxy.maxGas(), cfg.maxGas); assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); From 25c8104e1477fd38ece1251c5863d469eee502d9 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 12:40:18 +0300 Subject: [PATCH 39/57] Add token recovery funcs --- src/L1FarmProxy.sol | 6 ++++++ src/L2FarmProxy.sol | 5 +++++ test/L1FarmProxy.t.sol | 16 +++++++++++++++- test/L2FarmProxy.t.sol | 21 +++++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 8945a97..6a7411e 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -19,6 +19,7 @@ pragma solidity ^0.8.21; interface GemLike { function approve(address, uint256) external; + function transfer(address, uint256) external; } interface L1TokenGatewayLike { @@ -110,6 +111,11 @@ contract L1FarmProxy { require(sent, "L1FarmProxy/failed-to-send-ether"); } + // @notice Allow governance to recover potentially stuck tokens + function recover(address token, address to, uint256 amount) external auth { + GemLike(token).transfer(to, amount); + } + // @notice Estimate the amount of ETH consumed as msg.value from this contract to bridge the reward to the L2 proxy // as well as the RetryableTicket submission cost. // @param l1BaseFee L1 baseFee to use for the estimate. Pass 0 to use block.basefee diff --git a/src/L2FarmProxy.sol b/src/L2FarmProxy.sol index 71221c6..e0ba947 100644 --- a/src/L2FarmProxy.sol +++ b/src/L2FarmProxy.sol @@ -60,6 +60,11 @@ contract L2FarmProxy { emit File(what, data); } + // @notice Allow governance to recover potentially stuck tokens + function recover(address token, address to, uint256 amount) external auth { + GemLike(token).transfer(to, amount); + } + // @notice The transferred reward must exceed a minimum threshold to reduce the impact of // calling this function too frequently in an attempt to reduce the rewardRate of the farm function forwardReward() external { diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index 3f7f96a..6c7adf0 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -69,7 +69,8 @@ contract L1FarmProxyTest is DssTest { l1Proxy.deny(address(this)); checkModifier(address(l1Proxy), string(abi.encodePacked("L1FarmProxy", "/not-authorized")), [ - l1Proxy.reclaim.selector + l1Proxy.reclaim.selector, + l1Proxy.recover.selector ]); } @@ -89,6 +90,19 @@ contract L1FarmProxyTest is DssTest { l1Proxy.reclaim(to, 1 ether); // insufficient balance } + function testRecover() public { + address to = address(0x123); + rewardsToken.transfer(address(l1Proxy), 1 ether); + + assertEq(rewardsToken.balanceOf(to), 0); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), 1 ether); + + l1Proxy.recover(address(rewardsToken), to, 1 ether); + + assertEq(rewardsToken.balanceOf(to), 1 ether); + assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0); + } + function testNotifyRewardAmount() public { l1Proxy.file("rewardThreshold", 100 ether); diff --git a/test/L2FarmProxy.t.sol b/test/L2FarmProxy.t.sol index 5bb7b7f..266dc70 100644 --- a/test/L2FarmProxy.t.sol +++ b/test/L2FarmProxy.t.sol @@ -54,6 +54,27 @@ contract L2FarmProxyTest is DssTest { checkFileUint(address(l2Proxy), "L2FarmProxy", ["rewardThreshold"]); } + function testAuthModifiers() public virtual { + l2Proxy.deny(address(this)); + + checkModifier(address(l2Proxy), string(abi.encodePacked("L2FarmProxy", "/not-authorized")), [ + l2Proxy.recover.selector + ]); + } + + function testRecover() public { + address to = address(0x123); + rewardsToken.transfer(address(l2Proxy), 1 ether); + + assertEq(rewardsToken.balanceOf(to), 0); + assertEq(rewardsToken.balanceOf(address(l2Proxy)), 1 ether); + + l2Proxy.recover(address(rewardsToken), to, 1 ether); + + assertEq(rewardsToken.balanceOf(to), 1 ether); + assertEq(rewardsToken.balanceOf(address(l2Proxy)), 0); + } + function testForwardReward() public { l2Proxy.file("rewardThreshold", 100 ether); rewardsToken.transfer(address(l2Proxy), 100 ether); From c4d9f08c5b1f394d34c71e5e34fa85b1dda82a96 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 13:45:51 +0300 Subject: [PATCH 40/57] Recommend L2FarmProxy.rewardThreshold <= L1FarmProxy.rewardThreshold --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb6b880..acb623c 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This repository implements a mechanism to distribute rewards vested in a [DssVes - Once the vested amount of rewards tokens exceeds `L1FarmProxy.rewardThreshold`, a keeper calls `VestedRewardDistribution.distribute()` to vest the rewards and have them bridged to L2. - Once the bridged amount of rewards tokens exceeds `L2FarmProxy.rewardThreshold`, anyone (e.g. a keeper or an L2 staker) can call `L2FarmProxy.forwardReward()` to distribute the rewards to the L2 farm. -Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the frequency of cross-chain transfers (thereby also reducing the amount of ether that needs to be provisionned into the `L1FarmProxy`). `L2FarmProxy.rewardThreshold` must also be sufficiently large to limit the reduction of the farm's rate of rewards distribution. +Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the frequency of cross-chain transfers (thereby also reducing the amount of ether that needs to be provisionned into the `L1FarmProxy`). `L2FarmProxy.rewardThreshold` must also be sufficiently large to limit the reduction of the farm's rate of rewards distribution. Consider also choosing `L2FarmProxy.rewardThreshold <= L1FarmProxy.rewardThreshold` so that the bridged rewards can be promptly distributed to the farm. ## Deployment From 99af0883f758be88acfd6214b0e502157080e8cc Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 14:30:17 +0300 Subject: [PATCH 41/57] Use single rewardThreshold in init lib --- README.md | 2 +- deploy/FarmProxyInit.sol | 9 ++++----- script/Init.s.sol | 7 +++---- test/Integration.t.sol | 17 ++++++----------- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index acb623c..942db05 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This repository implements a mechanism to distribute rewards vested in a [DssVes - Once the vested amount of rewards tokens exceeds `L1FarmProxy.rewardThreshold`, a keeper calls `VestedRewardDistribution.distribute()` to vest the rewards and have them bridged to L2. - Once the bridged amount of rewards tokens exceeds `L2FarmProxy.rewardThreshold`, anyone (e.g. a keeper or an L2 staker) can call `L2FarmProxy.forwardReward()` to distribute the rewards to the L2 farm. -Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the frequency of cross-chain transfers (thereby also reducing the amount of ether that needs to be provisionned into the `L1FarmProxy`). `L2FarmProxy.rewardThreshold` must also be sufficiently large to limit the reduction of the farm's rate of rewards distribution. Consider also choosing `L2FarmProxy.rewardThreshold <= L1FarmProxy.rewardThreshold` so that the bridged rewards can be promptly distributed to the farm. +Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the frequency of cross-chain transfers (thereby also reducing the amount of ether that needs to be provisionned into the `L1FarmProxy`). `L2FarmProxy.rewardThreshold` must also be sufficiently large to limit the reduction of the farm's rate of rewards distribution. Consider also choosing `L2FarmProxy.rewardThreshold <= L1FarmProxy.rewardThreshold` so that the bridged rewards can be promptly distributed to the farm. In the initialization library, these two variables are assigned the same value. ## Deployment diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 5b88d3e..6ae7cf7 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -70,8 +70,7 @@ struct ProxiesConfig { address l1Gateway; uint256 maxGas; // For the L1 proxy uint256 gasPriceBid; // For the L1 proxy - uint256 l1RewardThreshold; // For the L1 proxy - uint256 l2RewardThreshold; // For the L2 proxy + uint256 rewardThreshold; // For the L1 and L2 proxies address farm; // The L2 farm uint256 rewardsDuration; // For the L2 farm MessageParams xchainMsg; // For the xchain message executing the L2 spell @@ -104,7 +103,7 @@ library FarmProxyInit { require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); - require(cfg.l1RewardThreshold <= type(uint128).max, "FarmProxyInit/l1-reward-threshold-out-of-bounds"); + require(cfg.rewardThreshold <= type(uint128).max, "FarmProxyInit/reward-threshold-out-of-bounds"); // setup vest @@ -123,7 +122,7 @@ library FarmProxyInit { l1Proxy.file("maxGas", cfg.maxGas); l1Proxy.file("gasPriceBid", cfg.gasPriceBid); - l1Proxy.file("rewardThreshold", cfg.l1RewardThreshold); + l1Proxy.file("rewardThreshold", cfg.rewardThreshold); // setup L2 proxy @@ -140,7 +139,7 @@ library FarmProxyInit { cfg.l2RewardsToken, cfg.stakingToken, cfg.farm, - cfg.l2RewardThreshold, + cfg.rewardThreshold, cfg.rewardsDuration )), l1CallValue: l1CallValue, diff --git a/script/Init.s.sol b/script/Init.s.sol index 6532ad0..5349415 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -65,7 +65,7 @@ contract Init is Script { address l2RewardsToken = deps.readAddress(".l2RewardsToken"); address stakingToken = deps.readAddress(".stakingToken"); address farm = deps.readAddress(".farm"); - uint256 l2RewardThreshold = 0; + uint256 rewardThreshold = 0; uint256 rewardsDuration = 1 days; bytes memory initCalldata = abi.encodeCall(L2GovernanceRelayLike.relay, ( @@ -75,7 +75,7 @@ contract Init is Script { l2RewardsToken, stakingToken, farm, - l2RewardThreshold, + rewardThreshold, rewardsDuration )) )); @@ -96,8 +96,7 @@ contract Init is Script { l1Gateway: deps.readAddress(".l1Gateway"), maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor - l1RewardThreshold: 0, - l2RewardThreshold: l2RewardThreshold, + rewardThreshold: rewardThreshold, farm: farm, rewardsDuration: rewardsDuration, xchainMsg: xchainMsg, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 6e92403..a4b7efa 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -206,8 +206,7 @@ contract IntegrationTest is DssTest { l1Gateway: l1Gateway, maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor - l1RewardThreshold: 1 ether, - l2RewardThreshold: 1 ether, + rewardThreshold: 1 ether, farm: address(farm), rewardsDuration: 1 days, xchainMsg: xchainMsg, @@ -229,28 +228,24 @@ contract IntegrationTest is DssTest { assertEq(vest.res(vestId), 1); assertEq(l1Proxy.maxGas(), cfg.maxGas); assertEq(l1Proxy.gasPriceBid(), cfg.gasPriceBid); - assertEq(l1Proxy.rewardThreshold(), cfg.l1RewardThreshold); + assertEq(l1Proxy.rewardThreshold(), cfg.rewardThreshold); assertEq(dss.chainlog.getAddress("FARM_PROXY_TKA_TKB_ARB"), address(l1Proxy)); assertEq(dss.chainlog.getAddress("REWARDS_DISTRIBUTION_TKA_TKB_ARB"), cfg.vestedRewardsDistribution); l2Domain.relayFromHost(true); // test L2 side of initProxies - assertEq(l2Proxy.rewardThreshold(), cfg.l2RewardThreshold); + assertEq(l2Proxy.rewardThreshold(), cfg.rewardThreshold); assertEq(farm.rewardsDistribution(), address(l2Proxy)); assertEq(farm.rewardsDuration(), cfg.rewardsDuration); } function testDistribution() public { - l2Domain.selectFork(); - uint256 l2Th = l2Proxy.rewardThreshold(); - l1Domain.selectFork(); - uint256 l1Th = l1Proxy.rewardThreshold(); - uint256 maxThreshold = l2Th > l1Th ? l2Th : l1Th; - vm.warp(vest.bgn(vestId) + maxThreshold * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId) + 1); + uint256 rewardThreshold = l1Proxy.rewardThreshold(); + vm.warp(vest.bgn(vestId) + rewardThreshold * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId) + 1); uint256 amount = vest.unpaid(vestId); - assertGt(amount, maxThreshold); + assertGt(amount, rewardThreshold); assertEq(l1Token.balanceOf(ESCROW), 0); vestedRewardsDistribution.distribute(); From fbe197f58cc7c73e2a295a050a9462f00b25cffb Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 17:50:47 +0300 Subject: [PATCH 42/57] Fix L2 spell and add L2 spell tests --- deploy/L2FarmProxySpell.sol | 3 +- test/L2FarmProxy.t.sol | 2 +- test/L2FarmProxySpell.t.sol | 180 ++++++++++++++++++++++++++++++++++++ test/mocks/FarmMock.sol | 30 +++++- 4 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 test/L2FarmProxySpell.t.sol diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index 325bc81..828ae69 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -22,6 +22,7 @@ interface L2FarmProxyLike { function rely(address) external; function deny(address) external; function file(bytes32, uint256) external; + function recover(address token, address to, uint256 amount) external; } interface FarmLike { @@ -39,6 +40,7 @@ contract L2FarmProxySpell { function rely(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).rely(usr); } function deny(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).deny(usr); } function file(address l2Proxy, bytes32 what, uint256 data) external { L2FarmProxyLike(l2Proxy).file(what, data); } + function recover(address l2Proxy, address token, address to, uint256 amount) external { L2FarmProxyLike(l2Proxy).recover(token, to, amount); } function nominateNewOwner(address farm, address owner) external { FarmLike(farm).nominateNewOwner(owner); } function setPaused(address farm, bool paused) external { FarmLike(farm).setPaused(paused); } @@ -57,7 +59,6 @@ contract L2FarmProxySpell { // sanity checks require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); - require(FarmLike(farm).rewardsToken() == rewardsToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token"); diff --git a/test/L2FarmProxy.t.sol b/test/L2FarmProxy.t.sol index 266dc70..77659a6 100644 --- a/test/L2FarmProxy.t.sol +++ b/test/L2FarmProxy.t.sol @@ -33,7 +33,7 @@ contract L2FarmProxyTest is DssTest { function setUp() public { rewardsToken = new GemMock(1_000_000 ether); - farm = address(new FarmMock(address(rewardsToken))); + farm = address(new FarmMock(address(rewardsToken), address(123))); l2Proxy = new L2FarmProxy(farm); } diff --git a/test/L2FarmProxySpell.t.sol b/test/L2FarmProxySpell.t.sol new file mode 100644 index 0000000..028269b --- /dev/null +++ b/test/L2FarmProxySpell.t.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; + +import { L2FarmProxy } from "src/L2FarmProxy.sol"; +import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; +import { FarmMock } from "test/mocks/FarmMock.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; + +contract L2FarmProxySpellTest is DssTest { + + GemMock rewardsToken; + address stakingToken = address(444); + address l2Proxy; + L2FarmProxySpell l2Spell; + address farm; + + event OwnerNominated(address newOwner); + event PauseChanged(bool isPaused); + event RewardsDurationUpdated(uint256 newDuration); + event RewardsDistributionUpdated(address newRewardsDistribution); + event Recovered(address token, uint256 amount); + + function setUp() public { + rewardsToken = new GemMock(1_000_000 ether); + farm = address(new FarmMock(address(rewardsToken), stakingToken)); + l2Proxy = address(new L2FarmProxy(farm)); + l2Spell = new L2FarmProxySpell(); + } + + function testL2ProxyFunctions() public { + bool success; + address usr = address(123); + + vm.expectEmit(true, true, true, true); + emit Rely(usr); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.rely, (l2Proxy, usr))); + assertTrue(success); + + vm.expectEmit(true, true, true, true); + emit Deny(usr); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.deny, (l2Proxy, usr))); + assertTrue(success); + + bytes32 what = "rewardThreshold"; + uint256 data = 456; + vm.expectEmit(true, true, true, true); + emit File(what, data); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.file, (l2Proxy, what, data))); + assertTrue(success); + + uint256 amount = 789 ether; + rewardsToken.transfer(l2Proxy, amount); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.recover, (l2Proxy, address(rewardsToken), usr, amount))); + assertTrue(success); + assertEq(rewardsToken.balanceOf(usr), amount); + } + + function testFarmFunctions() public { + bool success; + address usr = address(123); + + vm.expectEmit(true, true, true, true); + emit OwnerNominated(usr); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.nominateNewOwner, (farm, usr))); + assertTrue(success); + + vm.expectEmit(true, true, true, true); + emit PauseChanged(true); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.setPaused, (farm, true))); + assertTrue(success); + + uint256 amount = 456 ether; + vm.expectEmit(true, true, true, true); + emit Recovered(address(rewardsToken), amount); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.recoverERC20, (farm, address(rewardsToken), amount))); + assertTrue(success); + + vm.expectEmit(true, true, true, true); + emit RewardsDurationUpdated(amount); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.setRewardsDuration, (farm, amount))); + assertTrue(success); + + vm.expectEmit(true, true, true, true); + emit RewardsDistributionUpdated(usr); + (success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.setRewardsDistribution, (farm, usr))); + assertTrue(success); + } + + // from https://ethereum.stackexchange.com/a/83577 + function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) { + if (_returnData.length < 68) return 'Transaction reverted silently'; + assembly { _returnData := add(_returnData, 0x04) } + return abi.decode(_returnData, (string)); + } + + function testInit() public { + bool success; + bytes memory response; + + (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + address(0xb4d), + stakingToken, + farm, + 0, + 7 days + ))); + assertFalse(success); + assertEq(_getRevertMsg(response), "L2FarmProxySpell/rewards-token-mismatch"); + + (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + address(rewardsToken), + stakingToken, + address(0xb4d), + 0, + 7 days + ))); + assertFalse(success); + assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-mismatch"); + + (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + address(rewardsToken), + address(0xb4d), + farm, + 0, + 7 days + ))); + assertFalse(success); + assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-rewards-token-mismatch"); + + address badFarm = address(new FarmMock(address(rewardsToken), address(rewardsToken))); + address badL2Proxy = address(new L2FarmProxy(badFarm)); + (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( + badL2Proxy, + address(rewardsToken), + address(rewardsToken), + badFarm, + 0, + 7 days + ))); + assertFalse(success); + assertEq(_getRevertMsg(response), "L2FarmProxySpell/rewards-token-same-as-staking-token"); + + vm.expectEmit(true, true, true, true); + emit File("rewardThreshold", 888); + vm.expectEmit(true, true, true, true); + emit RewardsDistributionUpdated(l2Proxy); + vm.expectEmit(true, true, true, true); + emit RewardsDurationUpdated(7 days); + (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + address(rewardsToken), + stakingToken, + farm, + 888, + 7 days + ))); + assertTrue(success); + } +} diff --git a/test/mocks/FarmMock.sol b/test/mocks/FarmMock.sol index ce8eec3..151c89e 100644 --- a/test/mocks/FarmMock.sol +++ b/test/mocks/FarmMock.sol @@ -19,13 +19,41 @@ pragma solidity ^0.8.21; contract FarmMock { address public immutable rewardsToken; + address public immutable stakingToken; + + event OwnerNominated(address newOwner); + event PauseChanged(bool isPaused); event RewardAdded(uint256 rewards); + event RewardsDurationUpdated(uint256 newDuration); + event RewardsDistributionUpdated(address newRewardsDistribution); + event Recovered(address token, uint256 amount); - constructor(address _rewardsToken) { + constructor(address _rewardsToken, address _stakingToken) { rewardsToken = _rewardsToken; + stakingToken = _stakingToken; + } + + function nominateNewOwner(address _owner) external { + emit OwnerNominated(_owner); + } + + function setPaused(bool _paused) external { + emit PauseChanged(_paused); } function notifyRewardAmount(uint256 reward) external { emit RewardAdded(reward); } + + function recoverERC20(address tokenAddress, uint256 tokenAmount) external { + emit Recovered(tokenAddress, tokenAmount); + } + + function setRewardsDuration(uint256 _rewardsDuration) external { + emit RewardsDurationUpdated(_rewardsDuration); + } + + function setRewardsDistribution(address _rewardsDistribution) external { + emit RewardsDistributionUpdated(_rewardsDistribution); + } } From 296056514be1be0f7d3d541d67a6d54aac925b0e Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 17:54:59 +0300 Subject: [PATCH 43/57] Fix alignment --- deploy/FarmProxyInit.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 6ae7cf7..15d396a 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -93,17 +93,17 @@ library FarmProxyInit { // sanity checks - require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); - require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); - require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); - require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); - require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); - require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); - require(l1Proxy.feeRecipient() == l1GovRelay.l2GovernanceRelay(), "FarmProxyInit/fee-recipient-mismatch"); - require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); - require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); - require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); - require(cfg.rewardThreshold <= type(uint128).max, "FarmProxyInit/reward-threshold-out-of-bounds"); + require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); + require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); + require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); + require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); + require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); + require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); + require(l1Proxy.feeRecipient() == l1GovRelay.l2GovernanceRelay(), "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); + require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); + require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); + require(cfg.rewardThreshold <= type(uint128).max, "FarmProxyInit/reward-threshold-out-of-bounds"); // setup vest From f74d47abb752ee8a32876872d1395e32430d6576 Mon Sep 17 00:00:00 2001 From: telome <> Date: Wed, 10 Jul 2024 17:58:58 +0300 Subject: [PATCH 44/57] Fix spell revert msg --- deploy/L2FarmProxySpell.sol | 2 +- test/L2FarmProxySpell.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index 828ae69..f1bb556 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -59,7 +59,7 @@ contract L2FarmProxySpell { // sanity checks require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); - require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-rewards-token-mismatch"); + require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-staking-token-mismatch"); require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token"); L2FarmProxyLike(l2Proxy).file("rewardThreshold", rewardThreshold); diff --git a/test/L2FarmProxySpell.t.sol b/test/L2FarmProxySpell.t.sol index 028269b..df7ff4f 100644 --- a/test/L2FarmProxySpell.t.sol +++ b/test/L2FarmProxySpell.t.sol @@ -146,7 +146,7 @@ contract L2FarmProxySpellTest is DssTest { 7 days ))); assertFalse(success); - assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-rewards-token-mismatch"); + assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-staking-token-mismatch"); address badFarm = address(new FarmMock(address(rewardsToken), address(rewardsToken))); address badL2Proxy = address(new L2FarmProxy(badFarm)); From 7226d12910b1080975f868e5945fcd3e362d2666 Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 11 Jul 2024 09:53:48 +0300 Subject: [PATCH 45/57] Fix nits --- deploy/L2FarmProxySpell.sol | 4 ++-- src/L1FarmProxy.sol | 4 ++-- src/L2FarmProxy.sol | 4 ++-- test/L1FarmProxy.t.sol | 8 ++++---- test/L2FarmProxy.t.sol | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index f1bb556..5d14230 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -22,7 +22,7 @@ interface L2FarmProxyLike { function rely(address) external; function deny(address) external; function file(bytes32, uint256) external; - function recover(address token, address to, uint256 amount) external; + function recover(address, address, uint256) external; } interface FarmLike { @@ -40,7 +40,7 @@ contract L2FarmProxySpell { function rely(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).rely(usr); } function deny(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).deny(usr); } function file(address l2Proxy, bytes32 what, uint256 data) external { L2FarmProxyLike(l2Proxy).file(what, data); } - function recover(address l2Proxy, address token, address to, uint256 amount) external { L2FarmProxyLike(l2Proxy).recover(token, to, amount); } + function recover(address l2Proxy, address token, address receiver, uint256 amount) external { L2FarmProxyLike(l2Proxy).recover(token, receiver, amount); } function nominateNewOwner(address farm, address owner) external { FarmLike(farm).nominateNewOwner(owner); } function setPaused(address farm, bool paused) external { FarmLike(farm).setPaused(paused); } diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index 6a7411e..f3228a3 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -112,8 +112,8 @@ contract L1FarmProxy { } // @notice Allow governance to recover potentially stuck tokens - function recover(address token, address to, uint256 amount) external auth { - GemLike(token).transfer(to, amount); + function recover(address token, address receiver, uint256 amount) external auth { + GemLike(token).transfer(receiver, amount); } // @notice Estimate the amount of ETH consumed as msg.value from this contract to bridge the reward to the L2 proxy diff --git a/src/L2FarmProxy.sol b/src/L2FarmProxy.sol index e0ba947..091803d 100644 --- a/src/L2FarmProxy.sol +++ b/src/L2FarmProxy.sol @@ -61,8 +61,8 @@ contract L2FarmProxy { } // @notice Allow governance to recover potentially stuck tokens - function recover(address token, address to, uint256 amount) external auth { - GemLike(token).transfer(to, amount); + function recover(address token, address receiver, uint256 amount) external auth { + GemLike(token).transfer(receiver, amount); } // @notice The transferred reward must exceed a minimum threshold to reduce the impact of diff --git a/test/L1FarmProxy.t.sol b/test/L1FarmProxy.t.sol index 6c7adf0..2b0097b 100644 --- a/test/L1FarmProxy.t.sol +++ b/test/L1FarmProxy.t.sol @@ -91,15 +91,15 @@ contract L1FarmProxyTest is DssTest { } function testRecover() public { - address to = address(0x123); + address receiver = address(0x123); rewardsToken.transfer(address(l1Proxy), 1 ether); - assertEq(rewardsToken.balanceOf(to), 0); + assertEq(rewardsToken.balanceOf(receiver), 0); assertEq(rewardsToken.balanceOf(address(l1Proxy)), 1 ether); - l1Proxy.recover(address(rewardsToken), to, 1 ether); + l1Proxy.recover(address(rewardsToken), receiver, 1 ether); - assertEq(rewardsToken.balanceOf(to), 1 ether); + assertEq(rewardsToken.balanceOf(receiver), 1 ether); assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0); } diff --git a/test/L2FarmProxy.t.sol b/test/L2FarmProxy.t.sol index 77659a6..6037c55 100644 --- a/test/L2FarmProxy.t.sol +++ b/test/L2FarmProxy.t.sol @@ -63,15 +63,15 @@ contract L2FarmProxyTest is DssTest { } function testRecover() public { - address to = address(0x123); + address receiver = address(0x123); rewardsToken.transfer(address(l2Proxy), 1 ether); - assertEq(rewardsToken.balanceOf(to), 0); + assertEq(rewardsToken.balanceOf(receiver), 0); assertEq(rewardsToken.balanceOf(address(l2Proxy)), 1 ether); - l2Proxy.recover(address(rewardsToken), to, 1 ether); + l2Proxy.recover(address(rewardsToken), receiver, 1 ether); - assertEq(rewardsToken.balanceOf(to), 1 ether); + assertEq(rewardsToken.balanceOf(receiver), 1 ether); assertEq(rewardsToken.balanceOf(address(l2Proxy)), 0); } From 78aa99d39d509c3ee2df16454edd1d9d2d09bc07 Mon Sep 17 00:00:00 2001 From: telome <> Date: Sat, 13 Jul 2024 12:47:17 +0300 Subject: [PATCH 46/57] Fix excess fee collection logic --- .env.example | 6 +- README.md | 25 +++- deploy/FarmProxyDeploy.sol | 5 + deploy/FarmProxyInit.sol | 25 ++-- deploy/L2FarmProxySpell.sol | 14 ++- script/DeployL1FarmProxy.s.sol | 122 ++++++++++++++++++++ script/DeployL2FarmProxy.s.sol | 99 ++++++++++++++++ script/DeployL2Singletons.s.sol | 85 ++++++++++++++ script/Init.s.sol | 7 +- script/output/11155111/deployed-latest.json | 13 ++- src/EtherForwarder.sol | 37 ++++++ src/L1FarmProxy.sol | 22 +--- test/EtherForwarder.t.sol | 52 +++++++++ test/Integration.t.sol | 9 +- test/L2FarmProxySpell.t.sol | 21 ++++ 15 files changed, 487 insertions(+), 55 deletions(-) create mode 100644 script/DeployL1FarmProxy.s.sol create mode 100644 script/DeployL2FarmProxy.s.sol create mode 100644 script/DeployL2Singletons.s.sol create mode 100644 src/EtherForwarder.sol create mode 100644 test/EtherForwarder.t.sol diff --git a/.env.example b/.env.example index fe55e92..21d23cd 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,9 @@ ETH_RPC_URL= ARBITRUM_ONE_RPC_URL= SEPOLIA_RPC_URL= ARBITRUM_ONE_SEPOLIA_RPC_URL= -DEPLOYER= -PRIVATE_KEY=$(cat /path/to/pkey) +L1_DEPLOYER= +L2_DEPLOYER= +L1_PRIVATE_KEY=$(cat /path/to/pkey1) +L2_PRIVATE_KEY=$(cat /path/to/pkey2) ETHERSCAN_KEY= ARBISCAN_KEY= diff --git a/README.md b/README.md index 942db05..67a1327 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This repository implements a mechanism to distribute rewards vested in a [DssVes - `L1FarmProxy.sol` - Proxy to the farm on the L1 side. Receives the token reward (expected to come from a [`VestedRewardDistribution`](https://github.com/makerdao/endgame-toolkit/blob/master/src/VestedRewardsDistribution.sol) contract) and transfers it cross-chain to the `L2FarmProxy`. An instance of `L1FarmProxy` must be deployed for each supported pair of staking and rewards token. - `L2FarmProxy.sol` - Proxy to the farm on the L2 side. Receives the token reward (expected to be bridged from the `L1FarmProxy`) and forwards it to the [StakingRewards](https://github.com/makerdao/endgame-toolkit/blob/master/src/synthetix/StakingRewards.sol) farm where it gets distributed to stakers. An instance of `L2FarmProxy` must be deployed for each supported pair of staking and rewards token. +- `EtherForwader.sol` - A simple ether forwarding contract deployed on L2 to collect excess fee refunds and forward those to the `L2GovernanceRelay`. ### External dependencies @@ -57,10 +58,22 @@ Fill in the addresses of the L2 staking token and L1 and L2 rewards tokens in `s Fill in the address of the mainnet DssVest contract in `script/input/1/config.json` under the `vest` key. It is assumed that the vesting contract was properly initialized. On testnet, a mock DssVest contract will automatically be deployed. -The following command deploys the L1 and L2 farm proxies: +Start by deploying the `EtherForwarder` and `L2FarmProxySpell` singletons. You must use a deployment key for which the current nonce on L2 has been "burned" on L1 (i.e. has already been spent on L1 in a transaction that is not a contract creation transaction). This is required to make sure the address of the `EtherForwarder` can never contain code on L1. If that address ever had code on L1, it would no longer be usable as an excess fee refund receiver (see the reason why [here](https://github.com/OffchainLabs/nitro-contracts/blob/61204dd455966cb678192427a07aa9795ff91c14/src/bridge/AbsInbox.sol#L248)). ``` -forge script script/Deploy.s.sol:Deploy --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --verify --multi --broadcast +forge script script/DeployL2Singletons.s.sol:DeployL2Singletons --sender $L2_DEPLOYER --private-key $L2_PRIVATE_KEY --slow --multi --broadcast --verify +``` + +Next, run the following command to deploy the L2 farm and its L2 proxy: + +``` +forge script script/DeployL2FarmProxy.s.sol:DeployL2FarmProxy --sender $L2_DEPLOYER --private-key $L2_PRIVATE_KEY --slow --multi --broadcast --verify +``` + +Finally, run the following command to deploy the L1 vested rewards distribution contract and the L1 proxy: + +``` +forge script script/DeployL1FarmProxy.s.sol:DeployL1FarmProxy --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY --slow --multi --broadcast --verify ``` ### Initialize the farm L1 & L2 proxies @@ -68,13 +81,13 @@ forge script script/Deploy.s.sol:Deploy --sender $DEPLOYER --private-key $PRIVAT On mainnet, the farm proxies should be initialized via the spell process. To determine an adequate value for the `maxGas` storage variable of `L1FarmProxy`, the `Estimate` script can be run: ``` -forge script script/Estimate.s.sol:Estimate --sender $DEPLOYER --private-key $PRIVATE_KEY +forge script script/Estimate.s.sol:Estimate --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY ``` On testnet, the proxies initialization can be performed via the following command: ``` -forge script script/Init.s.sol:Init --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast +forge script script/Init.s.sol:Init --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY --slow --multi --broadcast ``` ### Run a test distribution @@ -82,11 +95,11 @@ forge script script/Init.s.sol:Init --sender $DEPLOYER --private-key $PRIVATE_KE Run the following command to distribute the vested funds to the L1 proxy: ``` -forge script script/Distribute.s.sol:Distribute --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast +forge script script/Distribute.s.sol:Distribute --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY --slow --multi --broadcast ``` Wait for the transaction to be relayed to L2, then run the following command to forward the bridged funds from the L2 proxy to the farm: ``` -forge script script/Forward.s.sol:Forward --sender $DEPLOYER --private-key $PRIVATE_KEY --slow --multi --broadcast +forge script script/Forward.s.sol:Forward --sender $L2_DEPLOYER --private-key $L2_PRIVATE_KEY --slow --multi --broadcast ``` diff --git a/deploy/FarmProxyDeploy.sol b/deploy/FarmProxyDeploy.sol index c73086b..f2212eb 100644 --- a/deploy/FarmProxyDeploy.sol +++ b/deploy/FarmProxyDeploy.sol @@ -21,6 +21,7 @@ import { ScriptTools } from "dss-test/ScriptTools.sol"; import { L2FarmProxySpell } from "./L2FarmProxySpell.sol"; import { L1FarmProxy } from "src/L1FarmProxy.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; +import { EtherForwarder } from "src/EtherForwarder.sol"; library FarmProxyDeploy { function deployL1Proxy( @@ -47,4 +48,8 @@ library FarmProxyDeploy { function deployL2ProxySpell() internal returns (address l2Spell) { l2Spell = address(new L2FarmProxySpell()); } + + function deployL2EtherForwarder(address receiver) internal returns (address forwarder) { + forwarder = address(new EtherForwarder(receiver)); + } } diff --git a/deploy/FarmProxyInit.sol b/deploy/FarmProxyInit.sol index 15d396a..34c4706 100644 --- a/deploy/FarmProxyInit.sol +++ b/deploy/FarmProxyInit.sol @@ -83,6 +83,7 @@ library FarmProxyInit { DssInstance memory dss, address l1Proxy_, address l2Proxy, + address etherForwarder, address l2Spell, ProxiesConfig memory cfg ) internal { @@ -93,17 +94,18 @@ library FarmProxyInit { // sanity checks - require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); - require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); - require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); - require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); - require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); - require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); - require(l1Proxy.feeRecipient() == l1GovRelay.l2GovernanceRelay(), "FarmProxyInit/fee-recipient-mismatch"); - require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); - require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); - require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); - require(cfg.rewardThreshold <= type(uint128).max, "FarmProxyInit/reward-threshold-out-of-bounds"); + require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch"); + require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch"); + require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch"); + require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch"); + require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewards-token-mismatch"); + require(l1Proxy.l2Proxy() == l2Proxy, "FarmProxyInit/l2-proxy-mismatch"); + require(l1Proxy.feeRecipient() == etherForwarder, "FarmProxyInit/fee-recipient-mismatch"); + require(l1Proxy.l1Gateway() == cfg.l1Gateway, "FarmProxyInit/l1-gateway-mismatch"); + require(cfg.maxGas <= 10_000_000_000, "FarmProxyInit/max-gas-out-of-bounds"); + require(cfg.gasPriceBid <= 10_000 gwei, "FarmProxyInit/gas-price-bid-out-of-bounds"); + require(cfg.rewardThreshold <= type(uint128).max, "FarmProxyInit/reward-threshold-out-of-bounds"); + require(etherForwarder.code.length == 0, "FarmProxyInit/forwarder-addr-has-code-on-l1"); // setup vest @@ -136,6 +138,7 @@ library FarmProxyInit { target: l2Spell, targetData: abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, + etherForwarder, cfg.l2RewardsToken, cfg.stakingToken, cfg.farm, diff --git a/deploy/L2FarmProxySpell.sol b/deploy/L2FarmProxySpell.sol index 5d14230..2f79c14 100644 --- a/deploy/L2FarmProxySpell.sol +++ b/deploy/L2FarmProxySpell.sol @@ -35,6 +35,10 @@ interface FarmLike { function setRewardsDistribution(address) external; } +interface ForwarderLike { + function receiver() external view returns (address); +} + // A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2 farms and their proxies contract L2FarmProxySpell { function rely(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).rely(usr); } @@ -50,6 +54,7 @@ contract L2FarmProxySpell { function init( address l2Proxy, + address etherForwarder, address rewardsToken, address stakingToken, address farm, @@ -57,10 +62,11 @@ contract L2FarmProxySpell { uint256 rewardsDuration ) external { // sanity checks - require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); - require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); - require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-staking-token-mismatch"); - require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token"); + require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch"); + require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch"); + require(ForwarderLike(etherForwarder).receiver() == address(this), "L2FarmProxySpell/forwarder-receiver-not-gov-relay"); + require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-staking-token-mismatch"); + require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token"); L2FarmProxyLike(l2Proxy).file("rewardThreshold", rewardThreshold); diff --git a/script/DeployL1FarmProxy.s.sol b/script/DeployL1FarmProxy.s.sol new file mode 100644 index 0000000..3e8d92f --- /dev/null +++ b/script/DeployL1FarmProxy.s.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; +import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; +import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; +import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; + +interface ChainLogLike { + function getAddress(bytes32) external view returns (address); +} + +interface AuthLike { + function rely(address usr) external; +} + +contract DeployL1FarmProxy is Script { + using stdJson for string; + + StdChains.Chain l1Chain; + StdChains.Chain l2Chain; + string config; + string deps; + Domain l1Domain; + Domain l2Domain; + address deployer; + ChainLogLike chainlog; + address owner; + address l1Gateway; + address vest; + address stakingToken; + address l1RewardsToken; + address l2RewardsToken; + address l1Proxy; + address vestedRewardsDistribution; + + function run() external { + l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); + deps = ScriptTools.loadDependencies(); + l1Domain = new Domain(config, l1Chain); + l2Domain = new Domain(config, l2Chain); + l1Domain.selectFork(); + + (,deployer, ) = vm.readCallers(); + chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); + l1Gateway = chainlog.getAddress("ARBITRUM_TOKEN_BRIDGE"); + l1RewardsToken = l1Domain.readConfigAddress("rewardsToken"); + + if (keccak256(bytes(l1Chain.chainAlias)) == keccak256("mainnet")) { + owner = chainlog.getAddress("MCD_PAUSE_PROXY"); + vest = l1Domain.readConfigAddress("vest"); + } else { + owner = deployer; + vm.startBroadcast(); + vest = address(new DssVestMintableMock(l1RewardsToken)); + DssVestMintableMock(vest).file("cap", type(uint256).max); + AuthLike(l1RewardsToken).rely(address(vest)); + vm.stopBroadcast(); + } + + // L1 deployment + + vm.startBroadcast(); + l1Proxy = FarmProxyDeploy.deployL1Proxy( + deployer, + owner, + l1RewardsToken, + deps.readAddress(".l2Proxy"), + deps.readAddress(".etherForwarder"), + l1Gateway + ); + VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ + deployer: deployer, + owner: owner, + vest: vest, + rewards: l1Proxy + }); + vestedRewardsDistribution = (VestedRewardsDistributionDeploy.deploy(distributionParams)); + vm.stopBroadcast(); + + // Export contract addresses + + // TODO: load the existing json so this is not required + ScriptTools.exportContract("deployed", "chainlog", deps.readAddress(".chainlog")); + ScriptTools.exportContract("deployed", "l2ProxySpell", deps.readAddress(".l2ProxySpell")); + ScriptTools.exportContract("deployed", "etherForwarder", deps.readAddress(".etherForwarder")); + ScriptTools.exportContract("deployed", "l1GovRelay", deps.readAddress(".l1GovRelay")); + ScriptTools.exportContract("deployed", "l2GovRelay", deps.readAddress(".l2GovRelay")); + ScriptTools.exportContract("deployed", "farm", deps.readAddress(".farm")); + ScriptTools.exportContract("deployed", "l2Proxy", deps.readAddress(".l2Proxy")); + ScriptTools.exportContract("deployed", "l2RewardsToken", deps.readAddress(".l2RewardsToken")); + ScriptTools.exportContract("deployed", "stakingToken", deps.readAddress(".stakingToken")); + + ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); + ScriptTools.exportContract("deployed", "vest", vest); + ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); + ScriptTools.exportContract("deployed", "l1RewardsToken", l1RewardsToken); + ScriptTools.exportContract("deployed", "l1Gateway", l1Gateway); + } +} diff --git a/script/DeployL2FarmProxy.s.sol b/script/DeployL2FarmProxy.s.sol new file mode 100644 index 0000000..ce9e8b3 --- /dev/null +++ b/script/DeployL2FarmProxy.s.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; +import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; +import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; + +interface ChainLogLike { + function getAddress(bytes32) external view returns (address); +} + +interface L1GovernanceRelayLike { + function l2GovernanceRelay() external view returns (address); +} + +contract DeployL2FarmProxy is Script { + using stdJson for string; + + StdChains.Chain l1Chain; + StdChains.Chain l2Chain; + string config; + string deps; + Domain l1Domain; + Domain l2Domain; + address deployer; + ChainLogLike chainlog; + address l1GovRelay; + address l2GovRelay; + address stakingToken; + address l2RewardsToken; + address farm; + address l2Proxy; + + function run() external { + l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); + deps = ScriptTools.loadDependencies(); + l1Domain = new Domain(config, l1Chain); + l2Domain = new Domain(config, l2Chain); + l1Domain.selectFork(); + + (,deployer, ) = vm.readCallers(); + chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); + l1GovRelay = chainlog.getAddress("ARBITRUM_GOV_RELAY"); + l2GovRelay = L1GovernanceRelayLike(payable(l1GovRelay)).l2GovernanceRelay(); + + // L2 deployment + + l2Domain.selectFork(); + + stakingToken = l2Domain.readConfigAddress("stakingToken"); + l2RewardsToken = l2Domain.readConfigAddress("rewardsToken"); + StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ + owner: l2GovRelay, + stakingToken: stakingToken, + rewardsToken: l2RewardsToken + }); + + vm.startBroadcast(); + farm = StakingRewardsDeploy.deploy(farmParams); + l2Proxy = FarmProxyDeploy.deployL2Proxy(deployer, l2GovRelay, farm); + vm.stopBroadcast(); + + // Export contract addresses + + // TODO: load the existing json so this is not required + ScriptTools.exportContract("deployed", "chainlog", deps.readAddress(".chainlog")); + ScriptTools.exportContract("deployed", "l2ProxySpell", deps.readAddress(".l2ProxySpell")); + ScriptTools.exportContract("deployed", "etherForwarder", deps.readAddress(".etherForwarder")); + ScriptTools.exportContract("deployed", "l1GovRelay", deps.readAddress(".l1GovRelay")); + ScriptTools.exportContract("deployed", "l2GovRelay", deps.readAddress(".l2GovRelay")); + + ScriptTools.exportContract("deployed", "farm", farm); + ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); + ScriptTools.exportContract("deployed", "l2RewardsToken", l2RewardsToken); + ScriptTools.exportContract("deployed", "stakingToken", stakingToken); + } +} diff --git a/script/DeployL2Singletons.s.sol b/script/DeployL2Singletons.s.sol new file mode 100644 index 0000000..5e75b3d --- /dev/null +++ b/script/DeployL2Singletons.s.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; +import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; + +interface ChainLogLike { + function getAddress(bytes32) external view returns (address); +} + +interface L1GovernanceRelayLike { + function l2GovernanceRelay() external view returns (address); +} + +contract DeployL2Singletons is Script { + StdChains.Chain l1Chain; + StdChains.Chain l2Chain; + string config; + Domain l1Domain; + Domain l2Domain; + address deployer; + ChainLogLike chainlog; + address l1GovRelay; + address l2GovRelay; + address l2Spell; + address etherForwarder; + + function run() external { + l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); + l1Domain = new Domain(config, l1Chain); + l2Domain = new Domain(config, l2Chain); + + // Check deployer's L2 nonce was burned on L1 + + (,deployer, ) = vm.readCallers(); + l2Domain.selectFork(); + uint256 l2Nonce = vm.getNonce(deployer); + l1Domain.selectFork(); + address next = vm.computeCreateAddress(deployer, l2Nonce); + require(next.code.length == 0, "Deployer's next L2 address has code on L1"); + uint256 l1Nonce = vm.getNonce(deployer); + require(l1Nonce > l2Nonce, "Deployer requires nonce burning on L1"); + + chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); + l1GovRelay = chainlog.getAddress("ARBITRUM_GOV_RELAY"); + l2GovRelay = L1GovernanceRelayLike(payable(l1GovRelay)).l2GovernanceRelay(); + + l2Domain.selectFork(); + + vm.startBroadcast(); + etherForwarder = FarmProxyDeploy.deployL2EtherForwarder(l2GovRelay); + l2Spell = FarmProxyDeploy.deployL2ProxySpell(); + vm.stopBroadcast(); + + // Export contract addresses + + ScriptTools.exportContract("deployed", "chainlog", address(chainlog)); + ScriptTools.exportContract("deployed", "l2ProxySpell", l2Spell); + ScriptTools.exportContract("deployed", "etherForwarder", etherForwarder); + ScriptTools.exportContract("deployed", "l1GovRelay", l1GovRelay); + ScriptTools.exportContract("deployed", "l2GovRelay", l2GovRelay); + } +} diff --git a/script/Init.s.sol b/script/Init.s.sol index 5349415..eb2542b 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -61,6 +61,7 @@ contract Init is Script { RetryableTickets retryable = new RetryableTickets(l1Domain, l2Domain, l1GovRelay, l2GovRelay); address l2Proxy = deps.readAddress(".l2Proxy"); + address forwarder = deps.readAddress(".etherForwarder"); address l2ProxySpell = deps.readAddress(".l2ProxySpell"); address l2RewardsToken = deps.readAddress(".l2RewardsToken"); address stakingToken = deps.readAddress(".stakingToken"); @@ -72,6 +73,7 @@ contract Init is Script { l2ProxySpell, abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, + forwarder, l2RewardsToken, stakingToken, farm, @@ -94,8 +96,8 @@ contract Init is Script { l2RewardsToken: l2RewardsToken, stakingToken: stakingToken, l1Gateway: deps.readAddress(".l1Gateway"), - maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin - gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor + maxGas: 70_000_000, + gasPriceBid: 1 gwei, // 0.1 gwei arbitrum_one_sepolia gas price floor * 10x factor rewardThreshold: rewardThreshold, farm: farm, rewardsDuration: rewardsDuration, @@ -115,6 +117,7 @@ contract Init is Script { dss, deps.readAddress(".l1Proxy"), l2Proxy, + forwarder, l2ProxySpell, cfg ); diff --git a/script/output/11155111/deployed-latest.json b/script/output/11155111/deployed-latest.json index 7d0e506..272215d 100644 --- a/script/output/11155111/deployed-latest.json +++ b/script/output/11155111/deployed-latest.json @@ -1,15 +1,16 @@ { "chainlog": "0x066eBcc55Ca699e14F3c3694CdB230a2B8cE3a83", - "farm": "0xde9763F97166B1c08cf3979675024cfB8ba0555D", + "etherForwarder": "0x0e52119de617fc0F64495CcDD67CF8c9083De339", + "farm": "0x53CdCEE25dE66009cea356Ac74B3e37d0229125C", "l1Gateway": "0x95a9f6c87F6BF487875c9C09e70A5c8DC9B41EF4", "l1GovRelay": "0x1Fc16121472E5990A112EC43266edf32E2a97fF8", - "l1Proxy": "0x7fFAF728d086C357D37A44CA065251a2aCd758FD", + "l1Proxy": "0xFB7716073036b616A5E20d82F3E2a94a83Eeb85D", "l1RewardsToken": "0x0B2eaB37Ab96685Ad2b1A6FdDb8921e156073f84", "l2GovRelay": "0xA87F8FFC547ca1613f0d22Ce288C39e1BBffEbf6", - "l2Proxy": "0x90C24Dd59CB87A4348a66D16E548959Cf5076ACC", - "l2ProxySpell": "0x135C3AB67098aad36B6a2cbfa15C50f1603a2DB0", + "l2Proxy": "0x3868A614cCF9C99a8636cf0c6D6057D869C6B3D3", + "l2ProxySpell": "0x26DA7B8FDeDbead1fDDf8A8B4585F2A9a7F7EF5F", "l2RewardsToken": "0x76F0DEE2c570401300f85c0Dc451780AD9f0e72c", "stakingToken": "0x7E6c1b028E73B912eC3e0D56537DC380A1805813", - "vest": "0xE9035355498Ce947e67E6958A6A9409eBd60C8B0", - "vestedRewardsDistribution": "0x6e4ee3Bc399E632c8a5bCdB65BbD88D5463857a5" + "vest": "0x148F291f0A83aa80660CFdfc2605D1e0387A489d", + "vestedRewardsDistribution": "0x2851cEA9c9a737f341D5Cd0e02aa7f9B0B24a32c" } diff --git a/src/EtherForwarder.sol b/src/EtherForwarder.sol new file mode 100644 index 0000000..3351069 --- /dev/null +++ b/src/EtherForwarder.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +contract EtherForwarder { + address public immutable receiver; + + event Forward(uint256 amount); + + constructor(address _receiver) { + receiver = _receiver; + } + + receive() external payable {} + + function forward() external { + uint256 amount = address(this).balance; + (bool sent, ) = receiver.call{value: amount}(""); + require(sent, "EtherForwarder/failed-to-send-ether"); + emit Forward(amount); + } +} diff --git a/src/L1FarmProxy.sol b/src/L1FarmProxy.sol index f3228a3..3c6e928 100644 --- a/src/L1FarmProxy.sol +++ b/src/L1FarmProxy.sol @@ -39,21 +39,6 @@ interface InboxLike { function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) external view returns (uint256); } -// From https://github.com/OffchainLabs/nitro-contracts/blob/90037b996509312ef1addb3f9352457b8a99d6a6/src/libraries/AddressAliasHelper.sol -library AddressAliasHelper { - uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); - - /// @notice Utility function that converts the msg.sender viewed in the L2 to the - /// address in the L1 that submitted a tx to the inbox - /// @param l2Address L2 address as viewed in msg.sender - /// @return l1Address the address in the L1 that triggered the tx to L2 - function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { - unchecked { - l1Address = address(uint160(l2Address) - offset); - } - } -} - contract L1FarmProxy { mapping (address => uint256) public wards; uint64 public maxGas; @@ -62,7 +47,7 @@ contract L1FarmProxy { address public immutable rewardsToken; address public immutable l2Proxy; - address public immutable feeRecipient; // L2 recipient of excess fee. Negative alias must be applied to it if the address contains code on L1 + address public immutable feeRecipient; // L2 recipient of excess fee. This address must never contain code on L1. InboxLike public immutable inbox; L1TokenGatewayLike public immutable l1Gateway; @@ -142,12 +127,9 @@ contract L1FarmProxy { (uint256 l1CallValue, uint256 maxSubmissionCost) = estimateDepositCost(0, maxGas_, gasPriceBid_); - // If the address of feeRecipient has code on L1, it will be aliased by the Arbitrum Inbox, which we want to cancel out here - address refundTo = (feeRecipient.code.length > 0) ? AddressAliasHelper.undoL1ToL2Alias(feeRecipient) : feeRecipient; - l1Gateway.outboundTransferCustomRefund{value: l1CallValue}({ l1Token: rewardsToken, - refundTo: refundTo, + refundTo: feeRecipient, to: l2Proxy, amount: reward, maxGas: maxGas_, diff --git a/test/EtherForwarder.t.sol b/test/EtherForwarder.t.sol new file mode 100644 index 0000000..8ce9d81 --- /dev/null +++ b/test/EtherForwarder.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; + +import { EtherForwarder } from "src/EtherForwarder.sol"; + +contract EtherForwarderTest is DssTest { + + EtherForwarder forwarder; + address receiver = address(123); + + event Forward(uint256 amount); + + function setUp() public { + forwarder = new EtherForwarder(receiver); + assertEq(forwarder.receiver(), receiver); + } + + function testForward() public { + (bool success,) = address(forwarder).call{value: 1 ether}(""); // not using deal() here, so as to check receive() + assertTrue(success); + uint256 receiverBefore = receiver.balance; + + vm.expectEmit(true, true, true, true); + emit Forward(1 ether); + forwarder.forward(); + + assertEq(receiver.balance, receiverBefore + 1 ether); + assertEq(address(forwarder).balance, 0); + + EtherForwarder badForwarder = new EtherForwarder(address(this)); + vm.expectRevert("EtherForwarder/failed-to-send-ether"); + badForwarder.forward(); + } +} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index a4b7efa..6dfbb27 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -168,6 +168,7 @@ contract IntegrationTest is DssTest { farm: address(farm) })); address l2Spell = FarmProxyDeploy.deployL2ProxySpell(); + address forwarder = FarmProxyDeploy.deployL2EtherForwarder(L2_GOV_RELAY); l1Domain.selectFork(); l1Proxy = L1FarmProxy(payable(FarmProxyDeploy.deployL1Proxy({ @@ -175,7 +176,7 @@ contract IntegrationTest is DssTest { owner: PAUSE_PROXY, rewardsToken: address(l1Token), l2Proxy: address(l2Proxy), - feeRecipient: L2_GOV_RELAY, + feeRecipient: forwarder, l1Gateway: l1Gateway }))); @@ -204,8 +205,8 @@ contract IntegrationTest is DssTest { l2RewardsToken: address(l2Token), stakingToken: stakingToken, l1Gateway: l1Gateway, - maxGas: 70_000_000, // determined by running deploy/Estimate.s.sol and adding some margin - gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum-one gas price floor * 10x factor + maxGas: 1_000_000, // determined by running deploy/Estimate.s.sol and adding some margin + gasPriceBid: 0.1 gwei, // 0.01 gwei arbitrum_one gas price floor * 10x factor rewardThreshold: 1 ether, farm: address(farm), rewardsDuration: 1 days, @@ -214,7 +215,7 @@ contract IntegrationTest is DssTest { distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB" }); vm.startPrank(PAUSE_PROXY); - FarmProxyInit.initProxies(dss, address(l1Proxy), address(l2Proxy), l2Spell, cfg); + FarmProxyInit.initProxies(dss, address(l1Proxy), address(l2Proxy), forwarder, l2Spell, cfg); vm.stopPrank(); // test L1 side of initProxies diff --git a/test/L2FarmProxySpell.t.sol b/test/L2FarmProxySpell.t.sol index df7ff4f..6db85af 100644 --- a/test/L2FarmProxySpell.t.sol +++ b/test/L2FarmProxySpell.t.sol @@ -19,6 +19,7 @@ pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; +import { EtherForwarder } from "src/EtherForwarder.sol"; import { L2FarmProxy } from "src/L2FarmProxy.sol"; import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol"; import { FarmMock } from "test/mocks/FarmMock.sol"; @@ -29,6 +30,7 @@ contract L2FarmProxySpellTest is DssTest { GemMock rewardsToken; address stakingToken = address(444); address l2Proxy; + address forwarder; L2FarmProxySpell l2Spell; address farm; @@ -42,6 +44,7 @@ contract L2FarmProxySpellTest is DssTest { rewardsToken = new GemMock(1_000_000 ether); farm = address(new FarmMock(address(rewardsToken), stakingToken)); l2Proxy = address(new L2FarmProxy(farm)); + forwarder = address(new EtherForwarder(address(this))); l2Spell = new L2FarmProxySpell(); } @@ -117,6 +120,7 @@ contract L2FarmProxySpellTest is DssTest { (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, + forwarder, address(0xb4d), stakingToken, farm, @@ -128,6 +132,7 @@ contract L2FarmProxySpellTest is DssTest { (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, + forwarder, address(rewardsToken), stakingToken, address(0xb4d), @@ -137,8 +142,22 @@ contract L2FarmProxySpellTest is DssTest { assertFalse(success); assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-mismatch"); + address badForwarder = address(new EtherForwarder(address(0xb4d))); (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, + badForwarder, + address(rewardsToken), + stakingToken, + farm, + 0, + 7 days + ))); + assertFalse(success); + assertEq(_getRevertMsg(response), "L2FarmProxySpell/forwarder-receiver-not-gov-relay"); + + (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( + l2Proxy, + forwarder, address(rewardsToken), address(0xb4d), farm, @@ -152,6 +171,7 @@ contract L2FarmProxySpellTest is DssTest { address badL2Proxy = address(new L2FarmProxy(badFarm)); (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( badL2Proxy, + forwarder, address(rewardsToken), address(rewardsToken), badFarm, @@ -169,6 +189,7 @@ contract L2FarmProxySpellTest is DssTest { emit RewardsDurationUpdated(7 days); (success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, ( l2Proxy, + forwarder, address(rewardsToken), stakingToken, farm, From a45c297b4682f7e2af12467b71a0c28b40e89773 Mon Sep 17 00:00:00 2001 From: telome <> Date: Sat, 13 Jul 2024 14:05:45 +0300 Subject: [PATCH 47/57] Remove old Deploy.s.sol --- script/Deploy.s.sol | 148 -------------------------------------------- 1 file changed, 148 deletions(-) delete mode 100644 script/Deploy.s.sol diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol deleted file mode 100644 index 5a5e90e..0000000 --- a/script/Deploy.s.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Copyright (C) 2024 Dai Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.21; - -import "forge-std/Script.sol"; - -import { ScriptTools } from "dss-test/ScriptTools.sol"; -import { Domain } from "dss-test/domains/Domain.sol"; - -import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; -import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; -import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; -import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; - -interface ChainLogLike { - function getAddress(bytes32) external view returns (address); -} - -interface L1GovernanceRelayLike { - function l2GovernanceRelay() external view returns (address); -} - -interface AuthLike { - function rely(address usr) external; -} - -contract Deploy is Script { - StdChains.Chain l1Chain; - StdChains.Chain l2Chain; - string config; - Domain l1Domain; - Domain l2Domain; - address deployer; - ChainLogLike chainlog; - address owner; - address l1GovRelay; - address l2GovRelay; - address l1Gateway; - address vest; - address stakingToken; - address l1RewardsToken; - address l2RewardsToken; - address farm; - address l2Spell; - address l2Proxy; - address l1Proxy; - address vestedRewardsDistribution; - - function run() external { - l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); - l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); - vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path - config = ScriptTools.loadConfig("config"); - l1Domain = new Domain(config, l1Chain); - l2Domain = new Domain(config, l2Chain); - l1Domain.selectFork(); - - (,deployer, ) = vm.readCallers(); - chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); - l1GovRelay = chainlog.getAddress("ARBITRUM_GOV_RELAY"); - l2GovRelay = L1GovernanceRelayLike(payable(l1GovRelay)).l2GovernanceRelay(); - l1Gateway = chainlog.getAddress("ARBITRUM_TOKEN_BRIDGE"); - l1RewardsToken = l1Domain.readConfigAddress("rewardsToken"); - - if (keccak256(bytes(l1Chain.chainAlias)) == keccak256("mainnet")) { - owner = chainlog.getAddress("MCD_PAUSE_PROXY"); - vest = l1Domain.readConfigAddress("vest"); - } else { - owner = deployer; - vm.startBroadcast(); - vest = address(new DssVestMintableMock(l1RewardsToken)); - DssVestMintableMock(vest).file("cap", type(uint256).max); - AuthLike(l1RewardsToken).rely(address(vest)); - vm.stopBroadcast(); - } - - // L2 deployment - - l2Domain.selectFork(); - - stakingToken = l2Domain.readConfigAddress("stakingToken"); - l2RewardsToken = l2Domain.readConfigAddress("rewardsToken"); - StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ - owner: l2GovRelay, - stakingToken: stakingToken, - rewardsToken: l2RewardsToken - }); - - vm.startBroadcast(); - farm = StakingRewardsDeploy.deploy(farmParams); - l2Spell = FarmProxyDeploy.deployL2ProxySpell(); - l2Proxy = FarmProxyDeploy.deployL2Proxy(deployer, l2GovRelay, farm); - vm.stopBroadcast(); - - // L1 deployment - - l1Domain.selectFork(); - - vm.startBroadcast(); - l1Proxy = FarmProxyDeploy.deployL1Proxy( - deployer, - owner, - l1RewardsToken, - l2Proxy, - l2GovRelay, - l1Gateway - ); - VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ - deployer: deployer, - owner: owner, - vest: vest, - rewards: l1Proxy - }); - vestedRewardsDistribution = (VestedRewardsDistributionDeploy.deploy(distributionParams)); - vm.stopBroadcast(); - - // Export contract addresses - - ScriptTools.exportContract("deployed", "chainlog", address(chainlog)); - ScriptTools.exportContract("deployed", "farm", farm); - ScriptTools.exportContract("deployed", "l2ProxySpell", l2Spell); - ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); - ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); - ScriptTools.exportContract("deployed", "vest", vest); - ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); - ScriptTools.exportContract("deployed", "l1GovRelay", l1GovRelay); - ScriptTools.exportContract("deployed", "l2GovRelay", l2GovRelay); - ScriptTools.exportContract("deployed", "l1RewardsToken", l1RewardsToken); - ScriptTools.exportContract("deployed", "l2RewardsToken", l2RewardsToken); - ScriptTools.exportContract("deployed", "stakingToken", stakingToken); - ScriptTools.exportContract("deployed", "l1Gateway", l1Gateway); - } -} From 880c283ec7d21ebd465f9eda576ff09a5343564d Mon Sep 17 00:00:00 2001 From: telome <> Date: Mon, 15 Jul 2024 15:32:23 +0300 Subject: [PATCH 48/57] Remove extra space --- script/Init.s.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/script/Init.s.sol b/script/Init.s.sol index eb2542b..137504b 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -40,7 +40,6 @@ contract Init is Script { Domain l1Domain; Domain l2Domain; DssInstance dss; - address l1GovRelay; address l2GovRelay; From d8204addb26e8ebf77907ba30107296317c9872e Mon Sep 17 00:00:00 2001 From: telome <> Date: Tue, 16 Jul 2024 21:05:11 +0300 Subject: [PATCH 49/57] Update README for env vars to be exported --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67a1327..806ab67 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the ### Declare env variables -Add the required env variables listed in `.env.example` to your `.env` file, and run `source .env`. +Add the required env variables listed in `.env.example` to your `.env` file, and run `set -a && source .env`. Make sure to set the `L1` and `L2` env variables according to your desired deployment environment. From 09ce9d8f9a8614580d397a9ef577cfaac9235776 Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 18 Jul 2024 17:55:01 +0300 Subject: [PATCH 50/57] Use single deploy proxy script with multiple deployers --- .env.example | 26 ++- README.md | 20 +-- lib/arbitrum-token-bridge | 2 +- script/DeployProxy.s.sol | 152 ++++++++++++++++++ ...ingletons.s.sol => DeploySingletons.s.sol} | 15 +- script/Distribute.s.sol | 4 +- script/Estimate.s.sol | 12 +- script/Forward.s.sol | 4 +- script/Init.s.sol | 4 +- 9 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 script/DeployProxy.s.sol rename script/{DeployL2Singletons.s.sol => DeploySingletons.s.sol} (89%) diff --git a/.env.example b/.env.example index 21d23cd..8016f33 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,12 @@ -FOUNDRY_SCRIPT_DEPS=deployed -FOUNDRY_EXPORTS_OVERWRITE_LATEST=true -L1="sepolia" -L2="arbitrum_one_sepolia" -ETH_RPC_URL= -ARBITRUM_ONE_RPC_URL= -SEPOLIA_RPC_URL= -ARBITRUM_ONE_SEPOLIA_RPC_URL= -L1_DEPLOYER= -L2_DEPLOYER= -L1_PRIVATE_KEY=$(cat /path/to/pkey1) -L2_PRIVATE_KEY=$(cat /path/to/pkey2) -ETHERSCAN_KEY= -ARBISCAN_KEY= +export FOUNDRY_SCRIPT_DEPS=deployed +export FOUNDRY_EXPORTS_OVERWRITE_LATEST=true +export L1="sepolia" +export L2="arbitrum_one_sepolia" +export ETH_RPC_URL= +export ARBITRUM_ONE_RPC_URL= +export SEPOLIA_RPC_URL= +export ARBITRUM_ONE_SEPOLIA_RPC_URL= +export L1_PRIVATE_KEY="0x$(cat /path/to/pkey1)" +export L2_PRIVATE_KEY="0x$(cat /path/to/pkey2)" +export ETHERSCAN_KEY= +export ARBISCAN_KEY= diff --git a/README.md b/README.md index 806ab67..94e63e1 100644 --- a/README.md +++ b/README.md @@ -61,19 +61,13 @@ Fill in the address of the mainnet DssVest contract in `script/input/1/config.js Start by deploying the `EtherForwarder` and `L2FarmProxySpell` singletons. You must use a deployment key for which the current nonce on L2 has been "burned" on L1 (i.e. has already been spent on L1 in a transaction that is not a contract creation transaction). This is required to make sure the address of the `EtherForwarder` can never contain code on L1. If that address ever had code on L1, it would no longer be usable as an excess fee refund receiver (see the reason why [here](https://github.com/OffchainLabs/nitro-contracts/blob/61204dd455966cb678192427a07aa9795ff91c14/src/bridge/AbsInbox.sol#L248)). ``` -forge script script/DeployL2Singletons.s.sol:DeployL2Singletons --sender $L2_DEPLOYER --private-key $L2_PRIVATE_KEY --slow --multi --broadcast --verify +forge script script/DeploySingletons.s.sol:DeploySingletons --slow --multi --broadcast --verify ``` -Next, run the following command to deploy the L2 farm and its L2 proxy: +Next, run the following command to deploy the L1 vested rewards distribution contract, the L2 farm and the L1 and L2 proxies: ``` -forge script script/DeployL2FarmProxy.s.sol:DeployL2FarmProxy --sender $L2_DEPLOYER --private-key $L2_PRIVATE_KEY --slow --multi --broadcast --verify -``` - -Finally, run the following command to deploy the L1 vested rewards distribution contract and the L1 proxy: - -``` -forge script script/DeployL1FarmProxy.s.sol:DeployL1FarmProxy --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY --slow --multi --broadcast --verify +forge script script/DeployProxy.s.sol:DeployProxy --slow --multi --broadcast --verify ``` ### Initialize the farm L1 & L2 proxies @@ -81,13 +75,13 @@ forge script script/DeployL1FarmProxy.s.sol:DeployL1FarmProxy --sender $L1_DEPLO On mainnet, the farm proxies should be initialized via the spell process. To determine an adequate value for the `maxGas` storage variable of `L1FarmProxy`, the `Estimate` script can be run: ``` -forge script script/Estimate.s.sol:Estimate --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY +forge script script/Estimate.s.sol:Estimate ``` On testnet, the proxies initialization can be performed via the following command: ``` -forge script script/Init.s.sol:Init --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY --slow --multi --broadcast +forge script script/Init.s.sol:Init --slow --multi --broadcast ``` ### Run a test distribution @@ -95,11 +89,11 @@ forge script script/Init.s.sol:Init --sender $L1_DEPLOYER --private-key $L1_PRIV Run the following command to distribute the vested funds to the L1 proxy: ``` -forge script script/Distribute.s.sol:Distribute --sender $L1_DEPLOYER --private-key $L1_PRIVATE_KEY --slow --multi --broadcast +forge script script/Distribute.s.sol:Distribute --slow --multi --broadcast ``` Wait for the transaction to be relayed to L2, then run the following command to forward the bridged funds from the L2 proxy to the farm: ``` -forge script script/Forward.s.sol:Forward --sender $L2_DEPLOYER --private-key $L2_PRIVATE_KEY --slow --multi --broadcast +forge script script/Forward.s.sol:Forward --slow --multi --broadcast ``` diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index c3c60c2..aac0feb 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit c3c60c2fcd870d48d6886c187c7cabb38c22ab76 +Subproject commit aac0feb4f834680ba5487d5eade2d4bb4a3012e3 diff --git a/script/DeployProxy.s.sol b/script/DeployProxy.s.sol new file mode 100644 index 0000000..e3815a8 --- /dev/null +++ b/script/DeployProxy.s.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Copyright (C) 2024 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { Domain } from "dss-test/domains/Domain.sol"; +import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; +import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; +import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; +import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; + +interface L1GovernanceRelayLike { + function l2GovernanceRelay() external view returns (address); +} + +interface ChainLogLike { + function getAddress(bytes32) external view returns (address); +} + +interface AuthLike { + function rely(address usr) external; +} + +contract DeployProxy is Script { + using stdJson for string; + + uint256 l1PrivKey = vm.envUint("L1_PRIVATE_KEY"); + uint256 l2PrivKey = vm.envUint("L2_PRIVATE_KEY"); + address l1Deployer = vm.addr(l1PrivKey); + address l2Deployer = vm.addr(l2PrivKey); + + StdChains.Chain l1Chain; + StdChains.Chain l2Chain; + string config; + string deps; + Domain l1Domain; + Domain l2Domain; + ChainLogLike chainlog; + address l1GovRelay; + address l2GovRelay; + address owner; + address l1Gateway; + address vest; + address stakingToken; + address l1RewardsToken; + address l2RewardsToken; + address l1Proxy; + address vestedRewardsDistribution; + address farm; + address l2Proxy; + + function run() external { + l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); + l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); + vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path + config = ScriptTools.loadConfig("config"); + deps = ScriptTools.loadDependencies(); + l1Domain = new Domain(config, l1Chain); + l2Domain = new Domain(config, l2Chain); + l1Domain.selectFork(); + + chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); + l1GovRelay = chainlog.getAddress("ARBITRUM_GOV_RELAY"); + l2GovRelay = L1GovernanceRelayLike(payable(l1GovRelay)).l2GovernanceRelay(); + l1Gateway = chainlog.getAddress("ARBITRUM_TOKEN_BRIDGE"); + stakingToken = l2Domain.readConfigAddress("stakingToken"); + l1RewardsToken = l1Domain.readConfigAddress("rewardsToken"); + l2RewardsToken = l2Domain.readConfigAddress("rewardsToken"); + + if (keccak256(bytes(l1Chain.chainAlias)) == keccak256("mainnet")) { + owner = chainlog.getAddress("MCD_PAUSE_PROXY"); + vest = l1Domain.readConfigAddress("vest"); + } else { + owner = l1Deployer; + vm.startBroadcast(l1PrivKey); + vest = address(new DssVestMintableMock(l1RewardsToken)); + DssVestMintableMock(vest).file("cap", type(uint256).max); + AuthLike(l1RewardsToken).rely(address(vest)); + vm.stopBroadcast(); + } + + // L2 deployment + + StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ + owner: l2GovRelay, + stakingToken: stakingToken, + rewardsToken: l2RewardsToken + }); + l2Domain.selectFork(); + vm.startBroadcast(l2PrivKey); + farm = StakingRewardsDeploy.deploy(farmParams); + l2Proxy = FarmProxyDeploy.deployL2Proxy(l2Deployer, l2GovRelay, farm); + vm.stopBroadcast(); + + // L1 deployment + + l1Domain.selectFork(); + vm.startBroadcast(l1PrivKey); + l1Proxy = FarmProxyDeploy.deployL1Proxy( + l1Deployer, + owner, + l1RewardsToken, + l2Proxy, + deps.readAddress(".etherForwarder"), + l1Gateway + ); + VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ + deployer: l1Deployer, + owner: owner, + vest: vest, + rewards: l1Proxy + }); + vestedRewardsDistribution = (VestedRewardsDistributionDeploy.deploy(distributionParams)); + vm.stopBroadcast(); + + // Export contract addresses + + // TODO: load the existing json so this is not required + ScriptTools.exportContract("deployed", "chainlog", deps.readAddress(".chainlog")); + ScriptTools.exportContract("deployed", "l2ProxySpell", deps.readAddress(".l2ProxySpell")); + ScriptTools.exportContract("deployed", "etherForwarder", deps.readAddress(".etherForwarder")); + ScriptTools.exportContract("deployed", "l1GovRelay", deps.readAddress(".l1GovRelay")); + ScriptTools.exportContract("deployed", "l2GovRelay", deps.readAddress(".l2GovRelay")); + + ScriptTools.exportContract("deployed", "farm", farm); + ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); + ScriptTools.exportContract("deployed", "l2RewardsToken", l2RewardsToken); + ScriptTools.exportContract("deployed", "stakingToken", stakingToken); + ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); + ScriptTools.exportContract("deployed", "vest", vest); + ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); + ScriptTools.exportContract("deployed", "l1RewardsToken", l1RewardsToken); + ScriptTools.exportContract("deployed", "l1Gateway", l1Gateway); + } +} diff --git a/script/DeployL2Singletons.s.sol b/script/DeploySingletons.s.sol similarity index 89% rename from script/DeployL2Singletons.s.sol rename to script/DeploySingletons.s.sol index 5e75b3d..d7a41c6 100644 --- a/script/DeployL2Singletons.s.sol +++ b/script/DeploySingletons.s.sol @@ -31,7 +31,11 @@ interface L1GovernanceRelayLike { function l2GovernanceRelay() external view returns (address); } -contract DeployL2Singletons is Script { +contract DeploySingletons is Script { + + uint256 l2PrivKey = vm.envUint("L2_PRIVATE_KEY"); + address l2Deployer = vm.addr(l2PrivKey); + StdChains.Chain l1Chain; StdChains.Chain l2Chain; string config; @@ -54,13 +58,12 @@ contract DeployL2Singletons is Script { // Check deployer's L2 nonce was burned on L1 - (,deployer, ) = vm.readCallers(); l2Domain.selectFork(); - uint256 l2Nonce = vm.getNonce(deployer); + uint256 l2Nonce = vm.getNonce(l2Deployer); l1Domain.selectFork(); - address next = vm.computeCreateAddress(deployer, l2Nonce); + address next = vm.computeCreateAddress(l2Deployer, l2Nonce); require(next.code.length == 0, "Deployer's next L2 address has code on L1"); - uint256 l1Nonce = vm.getNonce(deployer); + uint256 l1Nonce = vm.getNonce(l2Deployer); require(l1Nonce > l2Nonce, "Deployer requires nonce burning on L1"); chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); @@ -69,7 +72,7 @@ contract DeployL2Singletons is Script { l2Domain.selectFork(); - vm.startBroadcast(); + vm.startBroadcast(l2PrivKey); etherForwarder = FarmProxyDeploy.deployL2EtherForwarder(l2GovRelay); l2Spell = FarmProxyDeploy.deployL2ProxySpell(); vm.stopBroadcast(); diff --git a/script/Distribute.s.sol b/script/Distribute.s.sol index c0b8721..e17cd8a 100644 --- a/script/Distribute.s.sol +++ b/script/Distribute.s.sol @@ -34,6 +34,8 @@ interface L1ProxyLike { contract Distribute is Script { using stdJson for string; + uint256 l1PrivKey = vm.envUint("L1_PRIVATE_KEY"); + function run() external { StdChains.Chain memory l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path @@ -46,7 +48,7 @@ contract Distribute is Script { address l1Proxy = deps.readAddress(".l1Proxy"); (uint256 l1CallValue,) = L1ProxyLike(l1Proxy).estimateDepositCost(2 * block.basefee, 0, 0); - vm.startBroadcast(); + vm.startBroadcast(l1PrivKey); if (l1Proxy.balance < l1CallValue) { (bool success,) = l1Proxy.call{value: l1CallValue - l1Proxy.balance}(""); require(success, "l1Proxy topup failed"); diff --git a/script/Estimate.s.sol b/script/Estimate.s.sol index a05be6d..079130f 100644 --- a/script/Estimate.s.sol +++ b/script/Estimate.s.sol @@ -44,6 +44,9 @@ contract Estimate is Script { uint256 constant MAX_L1_BASE_FEE_ESTIMATE = 1 gwei; // worst-case estimate for l1BaseFeeEstimate (representing the blob base fee) returned from https://github.com/OffchainLabs/nitro-contracts/blob/90037b996509312ef1addb3f9352457b8a99d6a6/src/node-interface/NodeInterface.sol#L95 bool constant USE_DAI_BRIDGE = true; // set to true if the new token gateway isn't yet initiated + uint256 l1PrivKey = vm.envUint("L1_PRIVATE_KEY"); + address l1Deployer = vm.addr(l1PrivKey); + function run() external { // Note: this script should not be run on testnet as l1BaseFeeEstimate can sometimes be 0 on sepolia StdChains.Chain memory l1Chain = getChain(string("mainnet")); @@ -53,8 +56,7 @@ contract Estimate is Script { Domain l1Domain = new Domain(config, l1Chain); Domain l2Domain = new Domain(config, l2Chain); l1Domain.selectFork(); - - (, address deployer,) = vm.readCallers(); + ChainLogLike chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); address l1Gateway; address l1Token; @@ -69,9 +71,9 @@ contract Estimate is Script { bytes memory finalizeDepositCalldata = GatewayLike(l1Gateway).getOutboundCalldata({ l1Token: l1Token, - from: deployer, - to: address(uint160(uint256(keccak256(abi.encode(deployer, block.timestamp))))), // a pseudo-random address used as "fresh" destination address, - amount: uint128(uint256(keccak256(abi.encode(deployer)))), // very large random-looking number => costlier calldata + from: l1Deployer, + to: address(uint160(uint256(keccak256(abi.encode(l1Deployer, block.timestamp))))), // a pseudo-random address used as "fresh" destination address, + amount: uint128(uint256(keccak256(abi.encode(l1Deployer)))), // very large random-looking number => costlier calldata data: "" }); bytes memory data = abi.encodeWithSignature( diff --git a/script/Forward.s.sol b/script/Forward.s.sol index 3d030d6..0c18ded 100644 --- a/script/Forward.s.sol +++ b/script/Forward.s.sol @@ -30,6 +30,8 @@ interface L2ProxyLike { contract Forward is Script { using stdJson for string; + uint256 l2PrivKey = vm.envUint("L2_PRIVATE_KEY"); + function run() external { StdChains.Chain memory l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); StdChains.Chain memory l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); @@ -41,7 +43,7 @@ contract Forward is Script { address l2Proxy = deps.readAddress(".l2Proxy"); - vm.startBroadcast(); + vm.startBroadcast(l2PrivKey); L2ProxyLike(l2Proxy).forwardReward(); vm.stopBroadcast(); } diff --git a/script/Init.s.sol b/script/Init.s.sol index 137504b..d2da947 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -33,6 +33,8 @@ interface L2GovernanceRelayLike { contract Init is Script { using stdJson for string; + uint256 l1PrivKey = vm.envUint("L1_PRIVATE_KEY"); + StdChains.Chain l1Chain; StdChains.Chain l2Chain; string config; @@ -105,7 +107,7 @@ contract Init is Script { distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB" }); - vm.startBroadcast(); + vm.startBroadcast(l1PrivKey); uint256 minGovRelayBal = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; if (l1GovRelay.balance < minGovRelayBal) { (bool success,) = l1GovRelay.call{value: minGovRelayBal - l1GovRelay.balance}(""); From 702e425c7a5d3b7daad7105c1a260ba1f7996050 Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 18 Jul 2024 17:57:59 +0300 Subject: [PATCH 51/57] Remove unnecessary command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94e63e1..d1cd6bd 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Note that `L1FarmProxy.rewardThreshold` must be sufficiently large to reduce the ### Declare env variables -Add the required env variables listed in `.env.example` to your `.env` file, and run `set -a && source .env`. +Add the required env variables listed in `.env.example` to your `.env` file, and run `source .env`. Make sure to set the `L1` and `L2` env variables according to your desired deployment environment. From fa18c20b813d346dde8c6f49e4443b3c56080ad8 Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 18 Jul 2024 18:13:45 +0300 Subject: [PATCH 52/57] Update deps --- lib/arbitrum-token-bridge | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index aac0feb..643c62b 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit aac0feb4f834680ba5487d5eade2d4bb4a3012e3 +Subproject commit 643c62bd11c929cc47d0789b76f17022e21156de From 38476c1ff83f19c61465068659e0da410855559b Mon Sep 17 00:00:00 2001 From: telome <> Date: Thu, 18 Jul 2024 18:18:06 +0300 Subject: [PATCH 53/57] Remove outdated files --- script/DeployL1FarmProxy.s.sol | 122 --------------------------------- script/DeployL2FarmProxy.s.sol | 99 -------------------------- 2 files changed, 221 deletions(-) delete mode 100644 script/DeployL1FarmProxy.s.sol delete mode 100644 script/DeployL2FarmProxy.s.sol diff --git a/script/DeployL1FarmProxy.s.sol b/script/DeployL1FarmProxy.s.sol deleted file mode 100644 index 3e8d92f..0000000 --- a/script/DeployL1FarmProxy.s.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Copyright (C) 2024 Dai Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.21; - -import "forge-std/Script.sol"; - -import { ScriptTools } from "dss-test/ScriptTools.sol"; -import { Domain } from "dss-test/domains/Domain.sol"; -import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol"; -import { DssVestMintableMock } from "test/mocks/DssVestMock.sol"; -import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; - -interface ChainLogLike { - function getAddress(bytes32) external view returns (address); -} - -interface AuthLike { - function rely(address usr) external; -} - -contract DeployL1FarmProxy is Script { - using stdJson for string; - - StdChains.Chain l1Chain; - StdChains.Chain l2Chain; - string config; - string deps; - Domain l1Domain; - Domain l2Domain; - address deployer; - ChainLogLike chainlog; - address owner; - address l1Gateway; - address vest; - address stakingToken; - address l1RewardsToken; - address l2RewardsToken; - address l1Proxy; - address vestedRewardsDistribution; - - function run() external { - l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); - l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); - vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path - config = ScriptTools.loadConfig("config"); - deps = ScriptTools.loadDependencies(); - l1Domain = new Domain(config, l1Chain); - l2Domain = new Domain(config, l2Chain); - l1Domain.selectFork(); - - (,deployer, ) = vm.readCallers(); - chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); - l1Gateway = chainlog.getAddress("ARBITRUM_TOKEN_BRIDGE"); - l1RewardsToken = l1Domain.readConfigAddress("rewardsToken"); - - if (keccak256(bytes(l1Chain.chainAlias)) == keccak256("mainnet")) { - owner = chainlog.getAddress("MCD_PAUSE_PROXY"); - vest = l1Domain.readConfigAddress("vest"); - } else { - owner = deployer; - vm.startBroadcast(); - vest = address(new DssVestMintableMock(l1RewardsToken)); - DssVestMintableMock(vest).file("cap", type(uint256).max); - AuthLike(l1RewardsToken).rely(address(vest)); - vm.stopBroadcast(); - } - - // L1 deployment - - vm.startBroadcast(); - l1Proxy = FarmProxyDeploy.deployL1Proxy( - deployer, - owner, - l1RewardsToken, - deps.readAddress(".l2Proxy"), - deps.readAddress(".etherForwarder"), - l1Gateway - ); - VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({ - deployer: deployer, - owner: owner, - vest: vest, - rewards: l1Proxy - }); - vestedRewardsDistribution = (VestedRewardsDistributionDeploy.deploy(distributionParams)); - vm.stopBroadcast(); - - // Export contract addresses - - // TODO: load the existing json so this is not required - ScriptTools.exportContract("deployed", "chainlog", deps.readAddress(".chainlog")); - ScriptTools.exportContract("deployed", "l2ProxySpell", deps.readAddress(".l2ProxySpell")); - ScriptTools.exportContract("deployed", "etherForwarder", deps.readAddress(".etherForwarder")); - ScriptTools.exportContract("deployed", "l1GovRelay", deps.readAddress(".l1GovRelay")); - ScriptTools.exportContract("deployed", "l2GovRelay", deps.readAddress(".l2GovRelay")); - ScriptTools.exportContract("deployed", "farm", deps.readAddress(".farm")); - ScriptTools.exportContract("deployed", "l2Proxy", deps.readAddress(".l2Proxy")); - ScriptTools.exportContract("deployed", "l2RewardsToken", deps.readAddress(".l2RewardsToken")); - ScriptTools.exportContract("deployed", "stakingToken", deps.readAddress(".stakingToken")); - - ScriptTools.exportContract("deployed", "l1Proxy", l1Proxy); - ScriptTools.exportContract("deployed", "vest", vest); - ScriptTools.exportContract("deployed", "vestedRewardsDistribution", vestedRewardsDistribution); - ScriptTools.exportContract("deployed", "l1RewardsToken", l1RewardsToken); - ScriptTools.exportContract("deployed", "l1Gateway", l1Gateway); - } -} diff --git a/script/DeployL2FarmProxy.s.sol b/script/DeployL2FarmProxy.s.sol deleted file mode 100644 index ce9e8b3..0000000 --- a/script/DeployL2FarmProxy.s.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Copyright (C) 2024 Dai Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.21; - -import "forge-std/Script.sol"; - -import { ScriptTools } from "dss-test/ScriptTools.sol"; -import { Domain } from "dss-test/domains/Domain.sol"; -import { StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol"; -import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol"; - -interface ChainLogLike { - function getAddress(bytes32) external view returns (address); -} - -interface L1GovernanceRelayLike { - function l2GovernanceRelay() external view returns (address); -} - -contract DeployL2FarmProxy is Script { - using stdJson for string; - - StdChains.Chain l1Chain; - StdChains.Chain l2Chain; - string config; - string deps; - Domain l1Domain; - Domain l2Domain; - address deployer; - ChainLogLike chainlog; - address l1GovRelay; - address l2GovRelay; - address stakingToken; - address l2RewardsToken; - address farm; - address l2Proxy; - - function run() external { - l1Chain = getChain(string(vm.envOr("L1", string("mainnet")))); - l2Chain = getChain(string(vm.envOr("L2", string("arbitrum_one")))); - vm.setEnv("FOUNDRY_ROOT_CHAINID", vm.toString(l1Chain.chainId)); // used by ScriptTools to determine config path - config = ScriptTools.loadConfig("config"); - deps = ScriptTools.loadDependencies(); - l1Domain = new Domain(config, l1Chain); - l2Domain = new Domain(config, l2Chain); - l1Domain.selectFork(); - - (,deployer, ) = vm.readCallers(); - chainlog = ChainLogLike(l1Domain.readConfigAddress("chainlog")); - l1GovRelay = chainlog.getAddress("ARBITRUM_GOV_RELAY"); - l2GovRelay = L1GovernanceRelayLike(payable(l1GovRelay)).l2GovernanceRelay(); - - // L2 deployment - - l2Domain.selectFork(); - - stakingToken = l2Domain.readConfigAddress("stakingToken"); - l2RewardsToken = l2Domain.readConfigAddress("rewardsToken"); - StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({ - owner: l2GovRelay, - stakingToken: stakingToken, - rewardsToken: l2RewardsToken - }); - - vm.startBroadcast(); - farm = StakingRewardsDeploy.deploy(farmParams); - l2Proxy = FarmProxyDeploy.deployL2Proxy(deployer, l2GovRelay, farm); - vm.stopBroadcast(); - - // Export contract addresses - - // TODO: load the existing json so this is not required - ScriptTools.exportContract("deployed", "chainlog", deps.readAddress(".chainlog")); - ScriptTools.exportContract("deployed", "l2ProxySpell", deps.readAddress(".l2ProxySpell")); - ScriptTools.exportContract("deployed", "etherForwarder", deps.readAddress(".etherForwarder")); - ScriptTools.exportContract("deployed", "l1GovRelay", deps.readAddress(".l1GovRelay")); - ScriptTools.exportContract("deployed", "l2GovRelay", deps.readAddress(".l2GovRelay")); - - ScriptTools.exportContract("deployed", "farm", farm); - ScriptTools.exportContract("deployed", "l2Proxy", l2Proxy); - ScriptTools.exportContract("deployed", "l2RewardsToken", l2RewardsToken); - ScriptTools.exportContract("deployed", "stakingToken", stakingToken); - } -} From 69b539fb0cfbb75f36022981cbd70fb5d07fcf65 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:23:17 +0200 Subject: [PATCH 54/57] Add CS report (#3) Co-authored-by: telome <> --- ...nSecurity_MakerDAO_Arbitrum_Farms_audit.pdf | Bin 0 -> 468466 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/20240813-ChainSecurity_MakerDAO_Arbitrum_Farms_audit.pdf diff --git a/audit/20240813-ChainSecurity_MakerDAO_Arbitrum_Farms_audit.pdf b/audit/20240813-ChainSecurity_MakerDAO_Arbitrum_Farms_audit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..78166147e9d6d8e696ccdf9db7b91f85aacd25b4 GIT binary patch literal 468466 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%0AAI|QX#}QrZS!dmu;MH0l zU=UYozPVqeJG+KKAXtVzasRfx{0i(`QoFor?Tu0o2B)FZROr_B1^MXneM0PW;Fe7q z86;zs2PN08Hq*;`6I@H69Tirful&1O9)P6GX!rE(_Wn)aF+F@amg{BE{~%1UG0OCc zGd-Jp_)x}AkiT!X23OR+t)T&)f;;~_LmgH_SA~vcamX!4xSB8fFeUz-9ze7=U{R;{ zHV!YeH%{Je^R(fb2)?f~7tSP)%iq$3bRCVx#Cm=*24x;Nxe{sJL=L8|dSwRwA~|_aC zS#w^FKGmY#<8c;SkikBmyp#GAuyKP#u8&IcaTq+Ev^Rv;G26Sq_jBGjY*QbSWzHo_ zx6~~UVVJ|iF=_P~D2=PRSB-5~H6h@m_Cmi8|F-C{mood5EKfTjWXvwDZMH)d)|p~k zRaJP0qNTNKGfjg=nyuP!bwG^g zSyXGgNH3wF=){c_EK4KDKQz09sX+)xJe>{7t5+)AOpE19-qdr*lKng&+JZ_TI*F`% zv)?*iRuNsJodd6=ON7lVPnWlMXHICPww+|A>K3)8Qvin#0lRl<^YdPdMf_DS02NMJ zC(7rQ;eblLW;$Z)zqJPpzRr1MovW2Y zPV}bSLD2?%Y;{?krNfRIogE-iIRbL|`|?_93T%fEVa*Ap`Jr}E*1D{4(fAFizkL;@ zD%Xm7wkwsQn?4tu7cYXgB8NQws+Mr$-sN((zm@t2#W`!CIBejVbJckCH}a{TpSQ-7j86EEn4X8I_mH*C7Em=)kx$y(i z1DB1)262U}M1`WjpAx_11jqFtr5^8tJG8&B>mCJWI5~P3wJ$x9(c0rE1a$cBO%921 zJy#5CwUp1>g6XC>I=2YS#~4AVj(z68<8oj0vcw>N84|le1I_>9iBGioo=$PZK^!!tc(zWw1A`i z$Tu!vN|ehK*oKy1D|}B z>d#viLPEkNc6#oDBOqBlqaDxD^|7=G5h=vpDqesc=9G9%(mu!7jjjoWYh0edeZp}F z08L6#&Rx>!j5uy|mWj-}rN+QeGG1X;uDieAMtC_XS%CevZtx~+*hF7k+}&ZV zSWUmyZ@_IjEHcQCx_>+^AXy;B?JRi)vlN_55}?hxaQVe8qy6bcnfB; z?W>k^C29K@hp04KFUp;j>P}PrjJbDsy8*!(ur}rLIrZcwx6DZZyCO9@H6a8QR6vdJ zU4I*yXd+>4K%o<5WwID_Uvj0&a8P3|buu1$q`ys!rt!(oj@Q1z8A%-3`0Ug>`lf4S zv1ALG?t`pOGhR{Ja1s;MWo%z>EIK7C}!U9rk)@?UIvEZhL=k(#GB2DJe z>YiU+#jVA24hHPS<1|yJlhUiz?e9Nn61sW$Fyz03A_oET-TI!L2x;4EnZ6?cp6@#7i6fxQw1LOpO@ru+H5xn(<$~ z76*2QnjEj6`l6CUVUAaHcg=&*SA(bdx^Igl$Veb~5z754J{-KUj4W=4xwZxBKOlcG zMJk8YB%nE-AGkkUZ#H#Q*h0b#Kl6-zsnVGI+UlS);(psxYQn|ws-U+zZ@Q^(UZ6F& zonMxA%SgLN%_6My*AYtfdIE>q^{b>n^589{vl`@1IXP&0F1ld_z*Bs2*0e`;IQRrw zH?RFl2y~bx`l%s$iQ1Qg{*KTiBKv^_GME^i3)G-Prs(~`mblbc&_*76{_Pn=A2{v z2D@GH8JN77!wK8kRR|&c7is*r2PUs%;K#+j@ul2B)-8U%^8&k*3F!W$wOs`hw=L%c zOn5gO5O=`Nm5_gnV*Q#K7&mA*XAA9DMR+nzwz1yj` zMa$WhG^#+(v21#7>6S`D#>1Y?`954dgCP6BF(Z0R>OrZEX(v~FFe4ZgrV88QV=|RE zi1e}_B@ND;sWcCo=2EqnE4pm8Q7mlNt0Gn^G_YFyJF!^o%~C)b0udGb#g28VmNH9=MudF#M%~} z%R>Pa{(nBE!m?|pt#2Zo(@hH^k9MC5tW~ptJy>`aZd7-*gYC(&#Vl2Yr4>}$em?OD zF-x@gQylCwK(oB<)yd!;UTcfd2GF$jtj|wRC2y4RkCTGf=y*uD@YqD!b(V4eC*($8 z%(AEV@m)mpf5j4T&ic)Ae*%$()@43R2liqkG^Y7cDFd-Ir*Ob{secQop?uEhA4 z@ihYj9CbZz%SCKq>C*E!%8VT%CT*;q7se?@Q)P$>M2Z~A=`ha8qd9^J5u9$jQEec+=o}fb9ZoPaK4-NwOkv# z%yYanWo(e)h3K5#TCHkhJ@}5R_emxNEI+Y1f&mb>cEmgUK=T_;zqV;Af4rm3=t1oo zl@ajEx{vK7ds}994swN>C1f`~<&gqd#ii3b!6i^pMSV6N{>`sqB#~cNKVxP4wowLg9BxA7{SkNb0nXS#qZjJ>hv{M1NB4HvKyB z9E(qemdaxdc;>_t=cgsDL$kir_JvxVIGt1F73o@=g2UzQT9BQRMsv}l*YC6IO^e8R zHh>sL0`G|7C}@o*^mKBEmM%4d=&~Q{3s2~ut;>U+VbBB5b&cF?!E7zoKM{~xXwAvJ z_k$bNk6RgGRNr!jX9((l4m_!B5i!0nN zE>wr{J8OT?wC?Qif7p%uvdK?$dK+Z383E$Z;pRcSSoaJHiCZZU%lQjrVhZBslp2|k z|JhN_9`Gc2hhL$K^}gR zhk;p!gON>qTbK5}_d0B@{WM-D0>oI*3Of=UltO>Ll95*bW?VE4nnT z2)*)NkNfs2X?zSh)u>{_5!m1UUdPMDx;qstssM%o3HXX3!eI?_2?&0xc@rUj>YNW> zadoO(uR9nqs>*mg(A$g1@j|5XVVt6Lr-HRdP>D~F>B}uMxhKDU=TGRe-fsrqS5$A* zW)~{p-0Wl|ayOKInt*E6l<@5~_VTAb&O;O_0oO$UrV)h{wmN93WB-*wjox;dIzTGL z3%_B(y$JMs#&2Lrxr#*##UFrMTXvZ;C|p-@VSRFyptO){v-L zeGFuAe5&!n3fZ>5vwb^Hy#4TR7@w5)?HK?#*KX$+(8FZJ`cZB`6>31J%|TEfyJZm; zZQq}Tc`d!aHgEoKkGcK%G*(H^U6|zS!*^?iABSWR%Hv~h0cVZMVi6u!v*RR}vI@5k zk%!plHu}>H!oGPP2=?{7s^f}?)XJA<{oLLjeH2O#vEtyNSSd`rGdb85hYBM=wT=yjlu#&+U3b$@RjGq{^!Z zZ8z-Jfai)HEvC0~-NknWskG50vUysd{C$2YLU^nE3ZbSF2ZQg?YMilL_@}cF>yO6~ zEBM6iXz}S6b&k{|4=4|d`JZer&qN`N$M!#v?>1obJji%+uMa#;^ssjkFLpLta=))@ zWu#7h0lyvV<~7bkIB;?;0IzeU$K2Rt_}lQ`6M}^yQn|I*g;c`B-+;(kA)@7&b{+wlJ!87b)BtA}7!cVG&t2;Bdl**rdR??a7o)E0&TcyJTE)3neJSbtbg+kHqk3ubh*f^DM0y4JYPn~qE-I`Tx2dGF*~N?c zSN=3pjlAwCRZOY5x5egpsZxKZo$&jt#-6`^QxDc}?&SOYT~KNe))cSr2*|EMmk2j% zpQYK(_OZK-QbxXvB#Z7QHLKH%tkl-aNq{J|C%&Q^z0wUd7iy++OeGgd1Eu-jixb}O z&7=&pv5XAi3-^-%d>+*X$J;W6Lh{dGNh#r{`^XKp76h~SZ9RbGBLX@cIqCFMI1S0q zMApNrBd2Y(@h3yxgy+B(+S86vXNqu zbxfQ=a;u7A!czZ#<#LXpC=m=qgTmieEjE{P`B8E{k6&qbt!{IO-lfs2QLc@EVDd;I z*#9&$$Tp0g07Y;ghEY2RyCrPznNI_L=Y2E4f@IPrg%6i#pAizWdF9~bdrxsP9}dRk z=V>?C7{{Pv$6gxI=;%N*8=0-ZnKgJQel9Y-&6#fj+V^u~*1M+e<*8SY_ESCTkg>B} zxv^Jh9q;g0`bv_a*IiDa{fNvq`OVfdgmtF^Btt zmoJM?YF@Ip1T(rk3M>}Wa))z1Kz}T+JvOW^U#sj4-(~iwWCN@pRYNeM6sJ=)wTfA; zeA50}p{sIzJp~JS0x=&EP(rkP}tw=5v}2!UIrp+Y=m1cbI33+rFx+U6Gm^@o9HTTl_aty(YVW^oDCV zB?nxXA#S_TTzcu#x=?EjGJw+pu~ZBPbs)Sdy$+2#_|h|Jm>t=9UaL>6r8Uc^ij7GZ z>S6Te%|C&=RMC9(u4EqLzqFfYZ=Ri1wL*zdRi)GEjhcX*41Mo5Y)P*~D}Bbbi2%7) zW_=>+784^1jBFA5eP71E7oot?I` zdaT=rEc^lT8nqT)NSx2PBq;9QnYkS|*OXc6f>~n*1fybHtn@c|MH^Pv)%u;izqbDd zgEatA-=5@U-;&SSjf22{5sU!s@|eF%*`FIPlXbq4Z%;3Cx$TO2?@zQjsX(M2R1-)J z3^0qwV7FCp31moyn3=2ZARvafwg8fS{!-> zfS$XZDh7tk8n-WxN$dVZ#RITixdAogWO4Ag0sXryDF5c%_ z_i=YG|2txbt>f_xNpo`$gTjIgE_@JE&m>UTVB`6)I9%@v=5uW3-|LTXDn5?hsCNdg z8*A#wV16BWT(Up*dRJi%c^ue6ni4JcuUs0ovXGE=s!dAJl^_RZqZRIhAfy}Zl%<1! zzVaLRS9>KG!zCOzp1WWy%|Koc3svSeIZJnV)u2AeHp%?0GApc}^-}kH4vKiL+L-D< z<;kTXi?jJe$d743uN-pK8r4b(s)xp?9yi?Wu4#RT16XygUdr$``mu{&p^Q{g5dM~C zaS%zr8>qX=Hr+Af9#Z5yKvAh^rY&Dza5BBvCU^x&4Pi1AYyUq*h_Tw{h(@`(w;LX` zH2|vUPeCR9PfF@B`E9qKxw-g(oV*H^yZD6l9r7p%jnN_hm->5g29*nGRhlgK z%vFi5O2n#z69e4nWTS&gEgRPrG^qU%F*I6Z=VMdm4Y)zuqP2pLS%d2~Q@`t?f1Bp! z#28(^LlM9_E?N)WI{`=I#orUV_0XQwAy<4()tq^m96o>Y$VCd=GI>aI{mFjJID<%>TcH)4>(Fvi`Ij$*(M znO2bg7?R!(o_GT5<}VWfV#$?jh)s9XOLlXWTQIqm)(9M`a?n3!_GI)9O6Es35;&^a%%i70O%^7ueZm z4)Qb`Zcm#+Q<;2gkL{G;Ts`8NUDz#i>%Rl3w=2HVWXVL<#aO(ZM+>9do&0a3%3zSkB9EQ7phD~;aItBGWe5a0>=hnAE#G0u8RJt`&!uitABqWwZ< z+k6Y1;A&oE|Ckb&{xA*N`11sLGz8ze(5v*feadhA7Xci-pDHj*DR7pGr_&yQVp^$? z|C)Sbk|Qat+Viw^%Np-8oiao{$5UD5by@^HbKV427Bef^JRxN&Od>IDl{j^Xh4XTK z;`+CQH?DhY?3_7cD`l~J<T-zr}X}$5k3C-dKQDl)SVl!#{u*#CeUBi;_L2+RxzmP)ekqvxIkJbOF?qwKMId zM8wBbmF+z@EB;buSlVYe43eP=K1c(zoc05OY^01nnx+VDy6HhnNR{2$fgYa=stMw3 zia78+K`?U_G9A9FrR_$s`t+ZcauMC6L3Ax0w5hS79%Wn{7HnuP;6k$R1;B^%V9Goq z>L+P(IsQZ8lKyg%Es}3*tr5vpe+1wviemdkx|0hvCa{XE}*$n&E<%M~Vpe)|__h0X|A$2|( zr8?2j7OCaZ{;7oI zptXrr3CZ>Z=Bjl(T1v~{q!{;#T^I&q;6f>{8^kO83T+5!gTeahabgko0@mu@+_t}a z>Jv2G0@Iu40H$tmJ<-c~Femem54YLqKOQ}-0ND59xpgXrbhQ=r)(XNJKusB}L{Nf8 zrm$FUUc#sud>nv*Nyf99as}C^M^-L8CdI|n9rd2o+NIyky65|2f8U{%^{15`TVL&e z@_L}QYUU*GkVdyrAdX9E1t2$$#H43Di5Hnt^)_qQ_~s_{Bv01r%rN}oYadZldedsX zOU>{BKXiu6%wzX*>I0T{D!D(!7n~V(fN5wo@h4sUly;BmxvP(s?qLFExEfQczRg2; z>+ydr3!z!E&Jdrou)xq?-@qO0c5B^!-f+ovgPL8GSp(FY?mfw8tw(z`z#0 zO`8Aij0g^)s?cPuAbQ!|qel%~ISLQ@I)o|PO;FKHo|GhJz z!6Be$VF}@Y)g$)&nYF2}e@+P6&}l(~qsFSs>YCkh`2dejJruz8pxZjtAQhdcsdUR9 zgsZ<9@1qsryOme8YQ^r{t(G74_LymNkIeH1MCKcyxW|fnT`^t6-Y%t z)Ua!R{U}6ESUw#uzB5aRgHU{(<0+$T&DDjfDKB+u&bq#n6J0RXfFq*%pQ$Np7#%-_8o|f*#s=!dN)LM1oHq&laA=!=}C)~{5%Qaq16}tI)-5uwW-f}QH zW~8DR{i(_a=TDG<*>^K4*nNUE9&*0PB}Y5`Vy;JQ!0(69kIjZIjQfiD|9g%g38z~L z92nyz8R;0H;=NDn_1u<9?>dufnl@>}c=2or`&-fmsQX0Z$@sB%CdKf?57U=RmHa}A zx452#aqNivXu4=e`M4X5uYtd94=Eu`vQ+9~7r$<}KwcZ0BdbuadLZ2%;) z{``8KJB~5ul8@t281esm`Z6b0t>run){_=YDi1es!P47(`!Q->cTEQoBdtIfvkD9W++>9^$>S8-ks~7$M(L4iK6nWLbYL(aJDQ z4i9a%*fAl$V3FQzyGA;nbVX&l%qO6LPG5(h&jMQIwp#4$sh1LLczIvJAU_RyO;Z#- zTq0iJwxr`tYXrS*)`qodWp}K?QTN~9?rW39jC!pPud~@V+XJ9A6Ro44!2Iziz{nM2 zHa1tg*X?pX8TxJVUX0U!ZMi>_NEA((?Dn|R1tl}F7L|GH!*#7g-f488^6x9_5|kfq zCM8EFxfEpuEUbCJ)V_CH4&HAgU+HHOPFcvTTv$3eCJTPm@e?g?hJJYGqq2D2Jx9oL0?=est_x~ zkPvCNyj&TG2?rXDL5rnI0eS&P2m(Dc-4|2#t;YX0%?!5zEt~$5wWT@AFKs~}G#~ZK zGgEo`_p9NS=tAA`gUsNzC{MG$C!!2E!f!TF2n4Bn%BB>K?rv?JB*d+&zN%N^CKY${ zu!8fqlj7V3L3z8^jHR^0D9Yd~kCg1hO7T`<&R?HFShn(9aozqI*EL-5@*mfcNsPPc6FHgtknj|Zv{fb+(Sw33} zk+W0J4d1KncZ?ij-@jql?S^PR2)6mLw;R<@^7*FeslB9@29Qh3^#OW`wK~Oaxuqf% zG`X^T{Sv-a3hGsy7JfJC>=oFm&T};K6=+F%~e3dffZC zohrmB5j&QRs%wm$}XjgP~ z{VTLVe^PB+n{6i3ay}n8?A9HC)SPYO!oQa|w!g)ra=kZg6paR<5nDWdU0YCjYpHC> zLG?!`lb;VdTy9x2z_Ft%a+;}>>FRcP!!c-$XPs*C{w(gf4o{wBh&zS5l`ig2Lk}OV zb2VlYQzw1s!zAp+v@l0OZZKEck^N8o%qc=M!?!Za8h{n|Pn3HU2Mj#ph4#&@!`xQ{ zl9t@hL47s)Q?0Pw)y1}r?r|`zQfa@#lpzAH5W7icx|x7!5gmEI)T1Imj~XYY*b$_{ zEXg&zu2d&8$mADzOxF zK&a*`tX&VYS&FHYcIW5Jew(kKlODQd?xS#eNX|SjCL?}I)Qj^=NMtV2{?c!M2M;FZ z;v!W+>8hn_m#Y}3M!6WR{&iCpZ?OUCH+ci|MuT#6=G%TEFZM1ZSf}*Yb#;eH_#^U)>g%y?6{fGLOf=0L32K0kez-A< zXMdF<^3Ldb9rO#}QyQkS(~f^B+L=g_yI5%vw+6SfxsD%K4{IIeN#|9G#>Hvt*nC&( zJMc*q7EU3T{tuEEvf{Fx(pr_?(&!JG@~~NF!H&khH%%H8xQiK}mRh-TZBrIBH zJaTORKCje)qB}5j^Ki-}%XxRv(98lZr*nt(ay*P5n|`M48+Yj_xDzx-zDFFaVxN{K^XfJ{oZSJM{ZYeop?7-lhK2| z(j6n%YRNju?)aaZ24TF{4Cr8;_a^Qr0~Af}I#oT_i`PbJ0;QK-SqUdxnPjQU!eNNV zJGH+4m8ljYN(#|6Za@j0S~+{yyoIv$jDw zZuh%`2s{eUPsF!Joq2fpSl+DQu$(&s1>?S+TC(lio*BQ2G9~YvmW@aa!)R2l+W;oH zVDmo5+9qw*uSROqbo{HFJXfQ`Cm>uWudmROSBtA5hz_>nRpHLun(v7IZ|ZL^?fLS2 z?M-c30~X`y2`1`3n`QP*B?zM1rPy-{YVWXOUk{+C`p^l^h`{&L5(M~4(|!IbdFO;w z206K+g2kngf6ALaoiVJY=*R^f4adCG0Bk`1 z{c7sBX;kL?Dp=ZJceJK0!jTWr3wC7-&52$#t4}(E zkqi_bk?__lkt^=P7nqn!YJ&gf&1q@ss|PmT_-hw#gR?95^pXn}v#l#%LdsHWm4)rT z#%{UBy}3V^{-dRUat@{h>Npc`^<{XXakg=jdtR7f>34TRANu<3PJsnttK6c}$Y@6o zm0utFux$R9xUUs4OlPV~oUIgR3xaMgLtCGz$ z$oIh@+Q)PnNj0ZW_+*>v{s<%uDqx(980gn1w1E6cC*0=t>ebZLPRkErN6E;+FL>Tw zU8fOoGSU~9iqw*qdQ5M)mKDvi>J~Cy53}M6)FQR{P=%wX_c1pvl=;?O z>6bOrjs(yZh>Zk8*Q@QXb?HV8h7*`v=xQUF&!-sy49Zha zf{1Iu!n)vG28;S#DNRyk(YvWz6rbS`$#gs&Sf@2C-p>s_O_b#=X%v>N4*)4NJ9mx_ zS_dD)F*dyYXHM?Xi75dyt#{1Vt6*P!bwEOc?osZz3<`zpz^K2fvbWVbU#O#|*rKJq zu-`9G z=a4YI*jr)UAx_0&^JxE;Y=>9SjMzQa$Ll5vi7#NE=wI$e-J?mq)Wd%<`z(^7kfJk+Snaw>OqET6l~8m4ee(wj#mm6|RI@Gv_$Rm$~_E$LrSwV)g14)_uR{?d+@%IlS$ z&U4+&#%UCCY#w~i;;w---}SwV@bzZ*E!PgUR~d|=qHuW-fLLF?i5;m6( zLEKvat~|ojhR<$p*PD_l4B-?A2ZyLjC2sv-A-P(ANq-;7CT=xqy)ka?FXwhzBq4(M zr1!`5;bB>1w-B+jrvi5-am=0|=3B9CzDyhsCZPRg2l zz8kchL-h%%1}?!Ey}I*Fm0N5|U6O;vV)+!h$om~#7RVedFk4{acD0p|ZXclfMtpK@s)g(+`X z#`w<{2_?Ywx7LBtIz!?e=W}ufS~=v_)js*AYMpRrx&giKuRUE!?}X1SRbJ2FAV*s_ z`!(8Ie?RZ<%t_S8+N+R#b>1QJN%z2I4Umu_(;`58pM_!Rie8i_{kDqWPCHyaSBLcS zKkKN^&|;}ow~v0%_*Q3)Aq-2jN-b)@>A6*Z#;ZD$zKqCbYgTyO3lP!>Nr-ER0$lvz z06sPSkQ+|+GabXUN#owne8?2*90k7iG_bE}K_P+OeJRjfNR?@ZqW zk5PXX+=;eXn2@{0ksc5;25?OgBqtY{~VtfTb1ca7%OvaZLN zeZqb%Dl)cfDw8*Dx|5Qokh?L`MBgTH4e~y<1#>aW?Lq-1=Bt zRNJIEV$p#P$qF?xTUpag35>6~NLYIimCCJzB!=|@9RYhLR+v3U55sk|VjEt2Ad1fL z`P_Ezr9T9s{#+XNP3hf6HI(%r|DaAgI)&NL?<{rKF;^OGfcocO%CAPlZHhv%o_ci5qy0OwZM_6g}?4iT564ZyK|n! z^5pNdX>N)hVA+86jpw>=o!4?U^<*FcEHPCK=l(Gjz8r^9N=4ktuKxwgjL8VSJI0;7S{Sn zv%k3pHWyQU$Rnx;9j`g;pr%(3bXi_j?)dS%YNe0``z#l}##Ds z!1%h~kpagJ@8Iow-gk&w!mF1@9&yCv_pBv-OyU(ULLtRo@hl{JZ5zg-u^dU0x%AA9 z+7D#}uCbVq7JJEew&A9U0cfzlu7DoX9s{+nRQ?E^aifbQOnv5No!tm_vTH$B;Xy3& z&%nBkHY~ZyE{!VqjDQ~6&o=R`_uIF>sD>fvDWO~0qFU_yBtFOx7Ua~~iEhm$sF#;m3^v8fFUa5h13Y&(m~aDO(5t z1Nq40_?mY`x3m$I28`%@A>9x`K=%eD)4%5c9p`Q>CnK=&AuB8lW(#`0ByD1PoY-FAeTI7^G@V9k_f z5PJd!0(4kE_aZO(E7%6qYKtg^?W-`hEEpW`vTKSyAFhoXw_8XvQLLEx-Rf|1Mko37 z=OTbM(f6{=qtS3RxObH(UtMkfD#&;GF~b%Wa;%THL%uqA%Ac@Y=Gk`>LeDkajJOe? z1P4Gy|16-7kbk%HZ_l3pRk&KxX;%SkZ~*Cm?JknXiM9@u>L|Q?d%nH6{HnkaKO;ce z`%u!ic%L^B8(+=q+5w88cH3?P^-ChY)Xq;R9?@WSK|?YG=}c451RvMEw+Xf4jPTO> zgrgJry+UAdp<(!uDZcbipp_1WUg8yM)CVR4bbfm7{#DkXlhhjf2o36m-t4_KXChnK zU#68u)#3pk*j$1C`~9Jk;w&Z#3 zR&}R73gL4Qa5L&oAAC7`;gCX^GaCir+!rnd0Aj+wb3QEkHP7>M*uzh0hA;;oyR=hI zp20&SS*~ky6On}B zsAIGNWq&)8C*}WcCi4lhn1qAgIOGm~BubrY3FKpQp;yqMI;P6$}p447Q}JOCbRV!fV#4b z*0=B~L^T5WxR8s&@129T-jOkHTHyxbN-yxGxCU+S-{1h)fNp%L90tyrhNK$+OdQfR z-h7QsQ825`)p#KG7bCjF>GDcCyc^7`eQJJwHwfwj{g`ANA@klQ&fUHB(U31Wkw|`p z$5@>jEvVf9uMT9ediQOVp263N*0l@YuVr>eXl7LF*?a@C*1N+K^qunatAA)`+!*SK z6^}Z-^s2YRMbk)ExIyUTpm$JR^}Uk^QaaEVLRnV&ZahrO1oCI-oaUP<@NwD~YJXFk zof@`&RJDkw;^LU2+Z!0f`(TGjrRZP8HkfngHd27x`y#Z`;HDI!1s!8d_s2_q$t%9Ymx|D$!il1xYAQl?mFT#-dhwiIBF zyO)W(RQ$=*6TD!vd)V*=vy_|<=|N_)6>=K*gQWb-WJB8A387p_Azf`t?$d`(5gm;1 zdan{ol7F)J~-2VC32Fv8%Pjx@a5~~;w#~r3|p$hAcjx2Zl zdU3a=MKDaPb-IL?8p>6fyzHu|lHDm^Qo4GZpnlWnv;~u|(zZL-Hv_JGU+YpoebGjl zZmSO_(+km!^RC!FzfiLBH!(qyv~)O!@AJMw z?<;$A{;`kuw2yg*0<_8P5)XCMOWNePbCBVn1VeN5*PZ;PnwHLnj6U9tZ zNeKm!csE^j88Q?8i2_sQreLZUroLL;4F+Vf#OEEoa!Gd3#g0|SEPpp^w)$o9JFmsT@|{OM#f!jmPj9*W32@HKKtVpkLvP<(r?A5+ zr_avxG@m@0{aFJow)}geHK-#u7y;I|^Mmo_y3s@xc6FOI$SesR(9vJfOM#r@ZSt2* zk4W5Q0uRUloX}^d)lSzyD`nEcl)9f6GcngOKp!KA(Pgwc9}YnR3S?`AN`x5J}z#QSZ3+uj!9rsqeIMC1G{-y zW_0aom+DJ8ct#R7sqV;BzR_!RoEXU*|6|YIWnP<)0o60AtD_X~!)$7FRK{rc0M-)+ zY1~@xU7&03a+Pb_nqAze%#HZ#%l5Ngs}VDI*?yGWUDn1%SDC>Z^K1ASO2T-Q*PCc| z8&{PVUb$zY={0HO2i5uCvA#K}F!^_O8y2gnHegDfZ35{22xRfaOS)-PgOu4}wdLIPH*@wqrHj>NaLMl#T8esBxvH zDy-!b%|aAOs(9FQ9q2#M?M%|M!8~CL)dEei< zWHPV3AOG1;+`xK{ku5<_fT%4$4UUFEnIPB4tze?$SEK!HuXKS5itf(o!mPq&!yNv~ z+(zkoDoc2SRTdPQz)!+=>*U_*Ll3^XLU~UF1YEvWrRSZI2hW}T*vj`{c4a$@0X)M8 z1;W*;>hyU;A9c(A)+ga)&T>ZBN&zeg+<JZl4;a@AomYD33ts004+Oa~(l?kMpTJ%BG?f=^X?_ z4m%+i@_<$i_fa`WxScamIW^a=WoHc7SPSuXh;mSlgS@pi%uSWy%Gf4WJp|rYN8Nkx z6qWfP5R^TCgY;*Q{WpkDJ#WDrc8nb|0lv?3K|+?r6{8t0L=nF{xOaMdf9-bH39eG&@vab|hN=udF?%fiq6y5L8nV z{L-vbkKQv!p6;dJf52qWUdJ~kUx~t!bj2S`%o=kmpS#rzdZTmQ(txFAT3EV5T%+#= zHD$1RGqRgA3*2gQC~NoO@2h_&RSqA8+v;?nKD!4mH-i^x@}%U;EN|UfhrVN9@~47= z{O-?Uo|syDT`l%||F^!?Gwd^G6CODv7V&j?-KkO;d-Sr;PLyO{l?C}tjmx@77pZxh>l9k23-ib``cKIw`|aMff)@|V@Qwm zD$dJ?mzm%BYYy+eGigY%epz=4jN*DDPI>A|}Z&Mp*oIdfh=Mk{{HVmyi z^i0g$UT3-rF!1mJac>K4pZnl;P4}8iHQbwT2<}cVV7(zL`g>LvK+%5n=Thui2ZHv~ z8|zT$dk?p$N?&U0!^DkiEvLg=`#tMyuW&(3Xp*d%k$g; z)J<#^OgBDFkxikQd&Un~5v@p@9LV#^y`}@mqr#8vb#_mb7w6s<1UVQ?*5c1n=-ktd zZc;%r(8_&POH=_7Oosy!GMmfyH7`Z$nYvhrAS&}JL04(sM<9MS6hiS)Al#QUN|s0F zl(Hj@CF$iin-RYrog$bhZD+?~jLDZwoSuJLYe5=4(dzb~?YGrls<~^{Q-bZi;bs7? z2%0-JO~|pcM(tHvQh}^m;=%8Z`ADe?&>S)JPtp&}*&i zgETDuANJlm+?A^CA3b#5Lsz^awmr$Dw@I1Ir1#!tGU>@AnItpmGpUFhrKt!iBE^EB zprE2sR1`rh^lL#tUgRK(SOG<;Dxw1Sn|%(xyuaW3+~+>e{qNqhp9juPc4jiOX07j9 z>-+tbW|7CT(R^>SS0iv;h}a?f8sO3Zew1r!g?@0D)AV}oafY=ihIyvhkBpSX0O){4 zc-rDi=s>1sCKFQaPQ}yPyVZ7OI3G9{;XZvy)h{C*1*vh$7UH6BP`e9LM`Lct+lW|O zY$WH{rU*41kI8^$j)#ZiU^k(qA&2TJ+S}GacWLOvb=fqkm(!yGGXg)D%Gw?BXtBOI z0Te-bG#9J$Dj-&t;~tqc9$vXN!z{hjw`HIO#4OwM8L4hK7{f#WaT{O+fnm|G&&`J* zm)X;nCmv-T^Jb->V-k~l$nE9`sw$<0~sef;`#6td*hGP{mu%yMYC$&S36lmq{CPW-XlisrPy zSF!lO#gKqJ*<7$inD#uJZLK4NW-cTA*M`-yPO{jrr)7jPIIW2^QjuIbbPdu78~$yQ zobVAQ6Q%O4>Wq-%1w zkwlF`5NFl~fJ{;vUj;3h*xrIFs1|8srvaJ|ikQij7*`tYbyK;$R74Xqi?h4#FZjA@ zq~>C<*$DHgSlWC26IIGDnRm~E( z>P`i}Cxkl(gl=BatytX(G!L9k2UDmkViWx+vlk`jHrZoQpIOhh$^r8N7r#F{5de@Em{ey6z&%uJ#tJH)4Ji}3e7hOts$|~bAVQ-=ZcTPt zRjJ{#qGS2oZd48<2A=aoXDDwSVp<_N-dkPl)h1UhlnRxe zopU9fUNPlSh9G<`Y~tgC!0K}$XEL0lnpv9gW4P#1cQXX8}`%dRnHug>I zDaK)PFd9Yx0}3_oLO_QSW3oFJjTjR(&N}6d#lXs!8=}ml4wa0E1`&|@qB26A3AYVX z)#Di^6()Nz`DnTXj-0fOJ0XAP?Tzi-R;E;rs^<}%-J4%`m~o>h1aPue2=q!mmr7dc zSE)#kna#@MoN~kekV^(L8`VgQg4H{id)l>LycnP>g-oT|4MlrYG#bbCklpi-kwr=F z_9ezTg*XdEV3;eC8e1d_?MNuF0Jl`*WY;U7=apnTu3LDGbjJ)_fP&Us?06L27CHxx zY_X=|bZIAoQ#=Y|wh#i-J}85+Fj%l?BtGE1b|fG-!Xj;NjT;a5NJKRw%XxWUH3K)t zrTo=nnb;zpFN>bSNZ$?&2TUvN5`c^XC4V1h2&K}Ptwl~$hA6R@B0Mx$3CvIu1!d5x zS4yyx$mS$=1bmB~afhn~nNrngDw-Oj)TT9P(>f?exR$@Cwpcc6wvD`9wy2w@akX(+ zPk3FCmA5qR`s#`(;CB`5s-ROZ@VXS(2;qPQNPugkscCJ9Pnwes;+js|<9f?ZIyx(H zWMP&%S3-=-4R($o=NdL47!`ey@ka{OL>(G+?L$e4Ns!L~coIkoNk}zCdDF#0vQufA zx z6sP`@*U%~rfwDZu6%qAtRyB&8E8Ai+Hlaj!>mg0mVHIGtq#lQ~Y=>i6NGHao57>=uozN@_AwvfRr17vh6*dOp1_70gI~lJVRjAk?g$@>xf+Mb1 zl&yY+oM%c@GtS1t)<`>9O=a!PVITUQO}-r~CU`l4-w@GgC21p11Bhgl~C(6H*ZEeVB^v>*03qDFpTK|NEM<=_tKh| zGEny@1c%9Fhw+UPJN1HXdKt8t%(9|AGuDvSzK1Ki#O_?BX$&Jt&njdYcsu4kAU>2a zFF1_&6se@pyc*sVF62bI93H6v{ec#RrFu0HRWr`(o{tg^U9DT4+wmS00_`Qt zvjEGT8E}F)6!ajh!e$~cQ{Z8(jzT(cQA~QKW}|vq3=+PAX#g6cSdzeUJ#8@n>M5#) zY7(nWGQKo`Gf=Zdlsuq;r0E6YnBO+(2VrlOiQ4_vfmW-Y7^Miw<#TJQHxrhvHdz27 zu3-$VWTMANa9iEy}5MzhoqDQULhn%&r0w_&& z*Jd#^jKQuXWoIQ~#MvgWJ$x1PI47#{oF|Qr&7)euXI@pUwyxk^6v|CZ>S`BOSbw3z zG);maG-fcGB_?3dN6IcTYZP5tQ&Ap~B|$CAmKpZT?986CE8A5mVxVklB?<{XRiEYj zGTCg$cE!DJ(bk?zUhsF#@{xik{W{Kou8s7V!# z8oD!BZSwnSSfO^K1e2K81iP^t%POEsf!iA{|)p?7!votY~*Ez<-wmRlB}f9A+e!WifS*L@qjlq{Ll$NI(vF?K92!H zjH%-?y;=Y79qh0-Udl4+HJRZ%oB8zK#^wMNh1$;}B2D268>rTLo2Jm1E z5Lf|ciYREr2KdRe2Xrkp%0^6@Kn73?&3()Cq~6if>Um0Er7RO8)%O^5hKv`qzH>Ht zL#tMTITiJ+cZq-xVVT)*>W~&)$ZJjWradt0Q%asGyE=$&$~l%(CuH4y6>GX6 z%xCt@YKe@yNLA^SnC)2~2{{#N6J`O?U_6nDT6uZUgVV(#NNA+vK~-D}pbz76oEx(< z?SeyQ(WXi1(-j#@8fNVDBAs5@oIAT$4R4&gD>Ir)8;4bkzufA%LtLHeNwrm&wy1kk z$=wSmEzhGggwI~bsOa*%|VUo=aU^fy^jH_ZXjm9xZjA+ec zy$~MI-n|NkO>V^~1ERNXSN56?W?t>oaig|BPumMx2N>x2w4qF%vXJfdAX;M-WNXul z5L%_KfJ90t+p`iR2#pOTORQ!TOv6^mC=I;T@#cH`_K6rG zVf)b?JCmX1sA|zPP*84NCM#&Q>xXqpZ`I623$l$OVPwa`HtF`sGTC;!(qQ|rXU_9% z#zwg^`BiJihm+O~vUKCyS)d((6}$9ilp z$!ukX&9%t1Mmg58m$^r`D#`N7N9PAvh|x5x1{`;{h4y0Z^UYv8BV$Vv8X}@Q>9Uw9 zSdG4huUBVqzG7BuaCGG!B4N<7Ug%J10eWYORUA|x1(hQR7NnL40LS`?Q8Lx)$$YU2 zFw>j{8iXZYMTgKc^?a&R4NH?~Xd4{i>uPI5?05hrS18E4ptV3xl4!c&T zvVnH@AVLpm?K0me^(nFlM%oR4hQg0C7Yhu#)#zx>xp2plNU8h|6`hwLBQI+5VcWQ1 z4_PHmxt3E|DOWnoXNPff+r8o9Ae>tgmT3@IE(S>XzyRB6$Xwg1Fsb%~jM+F065xhx z)3?dU6srKdR=#4=A#Q`@SF`psZEM8xwh*hPd2?Pu>T^2QedO1I<2^Nf>R6H&em=Za)JR1jObO8bmwOt-69(d~SrB zlv!wpM~F=n({L(O%#^Jbbya1SW=p*I2ZE8s8)WE2z0@sO+eVgDn-Ds0m-EgY94z={d5TqOw(vNMzIxghCFhA^rIRxD zZ9YgsW6{{S4V{Bwk(S`fWhB>p6RqWh6(gkxrBUKjhY4?a;?07zN-$FsR#lSA!AL-S zEAUsENPvCNhC7Q^Z`O=AXKp80m9%!I3NBI2_9zk@)=GLSQs{v|pv+LCouS#xIz)q5 zBB-`##*UobIaH7?-rLAktuuz)Iab}2B(__VdDo+$#wp~@hSU@aPq<+!3+Ty|wU!*0 zy^RdVcvVrZCbg#{-p@}q;B3U93N16?31^YTGDVPUs#8<5{WUdNT}bO__baj5K)*q;=Z?E=^8aAuVMFHX&GFp@PgrIX4K$e9_T&+5va2@A|_klUG{t zd9gfH!R9(u&ux-92#s4!kpkbIXGt*{KhFs)VOhE-K9BeZ`?nc46y+Os!X*i4~x9gbOo&$JRDEt=gT= z>%c`Sr>wC@IHt!sVqxUOXl1j;e8mz2o`Y19B~0RUKwfJkVUp+<|= zi`I?<)6wyHgVQlSM z+I%P`0cX5WBipQ;)U$)SuA1>Vk|aDD#Zn)~B2L(jIO4P&?1z>!HJ;{npdM16N$3=~ zCf_XS8v_Sq@TQ4HWG%Dk3K;%k++3Y|__^S{LcQ-?bq)H1HlkFpE;YX-v?bC>P5cn;l!toiD{Q%rI?n z+vJNKY{HJ$EWj?1PNCayAevkx=rc#PKzEMf=GoZJ`;k;{ly+=9v>j2)WZ(x@X?yC3 zwiGa>qZyTU;pmxtp_I~4fy%#$^jI9h9bB>75Gv{>eYQ{%f=#O(bx95`?Whj*4zdy( zv}#xlAiITmwudO2xojzrXivnjXWi}QiWxUxjWUy%$0%0~TwIS~*=;ocIU((idVtg~p$n?k* z0V4Mlod>;bJTlJ>^%W>n#@*RrN1koY4Gm7tdS+6doI}Up62dGNbz!L;kk7@Qbc^XBrN4Jsb7?a8##Q!-auNB zNMw#l$^gsNW>{sM@CP9o!>9Tqbz*@Us$G;1F;A3nz6~e zifrVS(Hfx>rR#f%DiFAJJIN@Y)PF}hS8l2Q{!S0yBO7mfO| zw`B!XbQhE+b*qY#u$asx+O3xIOvi2AF^8KfXKgmZ+Iw0H-O8iwf_gQJSc-bkn{!GA zjYl_QeFo)7>rkhvg&>&LfCM>Cg{EoNHc^cx#b}+_{O0qrz4Vzew>HDj*WI4)cyp(*Xnn?+!>M|h_ z_pFtTfioNU^Z`&kI>ddpmb)i9*s@Lw3)aMFrk^=|t&m`_W=z^hdSVVr2`LKtZQ)azz zXk7;*-Om8x&)0T0Km>Z)h?bR*shnkI701BG%~FUSZ~?lC0T~oSEZv~^sNbh=r#1@%d7T+fs&cSa zRnA(B=(I<=y<)s9%v|{e6C}{KOOgd5;IL!mqY<^d&il(KBQ)xe)<+OQ=LCW)h^g56 z`k6L6oAL~;_7u7Gq6>nK0Wush7_2I4Sz0b9?sl4?nHKm@Xbjj8t}1LjWMMjv13OXmUTy}+j1`!0QU#}B%@t;y1qrM z8?sGoHii?YoN~emTBbIz<%BYqn5VV2AQ6aYDJ!Nb&YBWy;+F;}u_MS_>l`FY^_JG3 z%BeD)609E!MQIFV&$>D!O1g}-R;Qw16TrLMiHr|{gkxzvmCFzf74&sy27k!VTf_!2 zx13q@!)-OzDvr{=xW)?gScfN2?ZpiN!MMUDpKStg4Cwd53Xath-I>0tPf3nuD;ev> zQB`w_va|&&n?NN(t*3(P+1Q`~<$_{Sr^s@jnvE|UwRfC4$XZ{ ziklV2BXaIb)+Q=n+LopKVpeU|Bx~6XW>{`!s#0|(59I@kwkr(s20>sSP^wbb*C*{g zC86h%k)GPiHEU^^h2q9lHdK{OWlJq!RS+bhi(1X5iiD?3rM3ftvA<9T5@$Oomx=Mh z49i6rH0((Wp+PG*^f|{utiD;?cepxfibXOYu2~(0G%*3pq=A=26qlKZKMPazZth1LFF>5?W9DlLrTb_ z{@^&Ya5KWTDmxp^dxoi~9QM=1$V^0yCZ_D4t4G@L%(!u??DLTl;E-%K(b23$4a#ja zGV3?TjYWWUTWsY%Tk{$~gs{tI?UlK&6_452wJNHK&jMK~OhXcNz;JexbFz04r0`~p zV_iu{`;+Y{;Q*hVTv}$FTGK+l0!E?nyt2@0SRlSCSsHP>~%(Bo$2h}{J%(bm@Q~-n6>b5GEolJVVvQ1J+K>Z;z zJ#VyFUT{sR(^!8^qN*Q z9JXnq1tRP#jf_jUN}5=$)4pz~ zyH?ao>z;YN1m&YaCZA=ioSv(+OyN>|BSaueN9tn-(U{4GfiNUU6X3K8`gm&+@fU$> zW5={;8mo$P4>B9M)PYFY-I#3a*F{bn^?JQ=O*Okf{Oe%OrPp-?^@N+{J8>5|2_f=M zOg3w{P|d{dpb8*m84QYR8yVTg0ZW?zK~S)F3FIZ8t~bfSBWS{8g{d8!la2s1A&}%od`u9DOQ&tYNtGGc9J3UV_DbJsDG-;ru+>HyO~EPAq1 zAdLkF3~nam4tYn4mZ{uI=Cl^Mm?*2g5g?{^#97VN3x&P;zABp4ul%zbM1Z|TZ=GP& zz$9Rn*vhJ(Z0a(7;8MvjYcgiD5?Q^@)CNZZq&FUC=kyLeF6j=3v|12pK|4jz?NERz z%iLR6)MlD;kk>f!d}j$Jt_i_nug%wLZ+4i{4Go%|8Mxstx(35~mTa3@<($M?ro3{6 z2SE6<$7?NkLc!wcfJ2Jtym2~IY8Aks4u!mjus4rvFZnC5|EpBlN~K@ct7GLgB}){$ z{Ye7SjATR}^nyF^*a?_mLoTfv`^$A)a$cu zrOo0CFtU*?Ud{|bWYn2$$XOL&_F{ zN*Q;v+Dc)q^^a=}Wz@Cu0;FA$&5m{1Vu%ysaaV^|I!9hU+?u9_#YPFBij0D1mb_3T z(UEBAW_rt>TgL2lt4@YuE?%i0sj>qXk(|;MQ!L)_(UrPsq0Hm~-rE3YX#s&$VLvQ0h~ziH0g8&^W9~>S<%uSSJ;IkD?>eu6jae*Tu}BK)vTdq zDztV)QPxfgwa&4)2P+jbT-?K_tMPyVS%v0-Y#}pfY88u)CL1?;++ttC_?SiRQc=|~ zx1AWvfCeQq<-^*no1ZhWqR$l$C8=DIikT9gj<%RhWKr;~5*JxwZnV@_ZgVDF*1LwiONM)!!A1d17fsi*uA6x28;M6)&Zm>VvJkoVB zW?1Q?ph|$C$)T({F!mN1&Ja)7$Igi-)>wzf?qp~X0eRePtwkV;Qd7_33S?&mn~W>V zkj^AlTt3?NVdi`*#78|N#@r5hbFERJnHy=%oU+|vgxFpIilK2(L(>hPI)Ry*s!pxt z$aN~6G^fgXU39lH0?DorWpa;A6d*HNLoI6A37v>GYtI7Wkn{PHSQeo1t zG)<=Ifg_CcWG-8msl|)cFsT!&U2Wyx%Y!V(;hNUeJM0Z|gZzxOvaHzV8I`QBZDQ^a zSSnc!RMo4YI84NK@ugg2Ni$2f3Pq5a)?b)lVl~RKCArB~@MybYsbVS6NOeXsvniU^ z^qSxu65B|<0{oxsGJ`P!wK#$)r74ra=*F9{f&z-5dJu&i2*3>hvWz(gi#cT~~H zl)ILm7zLplaNA-l;2t_KqSfI%qet^KpmOfaXuWLEkEn{F#vIDcE$b9*sU6`;Dc0E* z{SYA1G_hQjCXwQ7M6{N8zuS}fHIV&v)#b6!!U*&me(!2gS9H| z12v%5ARkSJo3Wi)TOXBT$&P-cE;&8Z09#}Rj6TqnncYGG7}uLlOEk+4Q3tj_Sx2`b zs|Fgrfv)c`0zxWXs`X>?WzS6b^1%T=*E9ohp&7L&zyVJL)lf}l5&@jI;4RWazNGMQ z1c*oWqi};!OLv`jIh$=-ETxti4A%!_BWKLmk!yFh;xzbQ@Lroiuu75eG{)e)+i_*ROz1G3~j9RNu^Xmz7|665@Ie5 zl9u*xjjp&}Yb2%@5ih5x!Y%+Fjb^cM7AsY)t~6F?Ositiz)9ISB}`!QE@k{F4-g*! zSr1$&W1J}y!K88@8M`qcU{(T+#&nZ5L?^Sn(!np$$t2Y=wZ0D4VPJATx{ed1xfu~^;8AQAGg1XHXoSv+R5jiI%WK}jH zWm#oWt68Z-^rBg`r6bJ%nqHYEuMWAwN-9zv9#Oc3JTDW%>KfM3Q{6txR1><9l`Um) zTH3U8!_jq-5xSu1ud8c&C=-naGcw+*Mn*kDi_jtoOh=3Z9iJ!TPBP0rqII)Hb0rR` zXAoxajDkUWTB|`2SsUyFUkR%l;zCw8$t%}JUegbl>j_0Q?BarAvb#}#EyaDQKDnKX z$f7BGU)81BIU}B^57p)l)0i)&I-veWhO(T5Lg2D<>Acy>)Th&_Ct9A3A$zBw0@NU2 z9v7!<3$KIng1pvPs>Qmb9657^g%^Z(&E727tn+oot!R3(a-D0r6WhQWFx;|5inh8V z>mM8IOf4p#W{Ik*gcpHd%@IQc0h>?#VpM5rrM6Tz%qn~}2O70{Fy#rXHs}hZjX43> z!H?|AdR`bk{?;)sutbXVxExP72rDRFa{GHn$Qucnrl4)Wj=Cg zVX)}RIY=|{D#1%nCUd%m%}A@VsnJJCLk}hk+4fKaE}lwRp*|Gs zp1>SZxO}ym11Dj=-R_Hedk`^AvyNmbWH$AE0!lT!6Hr=E_lLj=-h8p33u^P}S;af2 zDr;IL8NyjS`&sPz3|7P^x8!N)Ec1Y7y6^bO3ZV{#zpNW=5FqB3T$)$DRZ8S_-V3)=%(^(X8R>mxl1d4+efR^!Tzt4|~g4S%F)l}8a zq?9O7Ku*hz!p2Ocl&h6tgT=LFS|W`G;E(YA(8{zgq_db#c5VWEWI%n5;vSaWrBjhWME~JV6q9KZS63^>Z9{j zI|uTkMBKi23>g#Mx>l6tev__N5}a$25Ga2aOycrG_c-Dd>Pvsh>(R600hJAr&J|{y z1nr6*$hVfahptpQkd7f4m^SMcu&aue&7~sGcl@#WKWB%dwO?5#u-qh;mVZaNdpu`9;)0X!Cg9FytbbH{zXZra;*CCTK@M zx!r6c(XLWPON@7>(T0giq7p4>hLgPCTprp`WD+IJw6>*LTE^<6DESQ8ge5BjV61#F z3(4201BBDSpk#(wmlb>>X}#uAaLGWeJ}qD5$`$C=E<2}FY4J2lEGeOFL7-|Ww-g|S zjgM8CL0ipf)y4i|78(_!W!5wwcPz4C9Yke%lXP3d!i)eDZB+6LdX|W`CRxd`i8FF3 z;FDF_Ui*lhg+m!mkeh_{!f9~zeO&@-;)n)hJkx0jBL_XcN$LgKQ!chfYN!B#@9a1m zpfEtuXWhjbh$hdua?6mdFI%auN1vH)w*3XfLgxXZqU#itsAIBJq=Fe_z8r%8ZQC#g zWfS$BPk7w8-^YZ5!!A$Rz^<}keu!$sSi_zbj@)W~X)OKBho-g+9);PJ^QC+aIf_|c? z+3d?+rqcAMWyP)(*C^FRM5jvlt=g4q2z?%1=>{>sB}Nm?Fl3!34zg%lTQ;fETxkGm z&1_W_ShZGdk6oJqmn~Uz5jQq?$*p(DQoJ7bhGM3Y1*(`k1DOih&56C6+EkO6Cy@A3 z5pt>mvUM*?7+W<{v7O&e0!KM(OZ)g<0g-I&aFa|!!c1f7 z3}s7c)Gdm0Iw$$Yzy&dhVFF*Rb|hT5Y%gab=J9yP7$13)?Zt?-Mt#YKJyNb!-4O(f zrn2!=uR3Yc^lUs~t@(H`>6}4FuBr@>tpX?Wa%2S{?Ukn$@mbbd@G|qYXxTVi_wwfc z04Cs2i4DaHsti=nLG82_s&C_#lCe^Nva8*!b=DlQc`y?K*U*LNceZjV^^Yk}aem77VLm3#C5M@J)h) zE@PtY9y94{&C9gZ&ASr5c-x@S!G;_hu5GJ&AZc1)O2ih@OzqxymU1^Na+9dF>v9px zsyhhd1?`|^Agi7@-Dtz=HP4JI+T^moge$MLY%3Hn;sb?~5-0j`fK5wEr{CFJB#fEN zIPT%vQJP756r|1936GuWs0oTfYOM>@QQ0oLnkhU|UEQVt7A{Y@C$5kqno6ZOgh<(| zSs)dNSl*Wk8qD67F+)vLijH4X=~I(*k3&>ri2IYptf8Lyhd#f_G@eM6xhQK?OA#eG zDpROqM2x3x4%2rOiBE9LbIM3TR$tfgJtBqPMHry=LfaEO;rc52N<4a6xMvYt< z1jLBa!s{1JL$E}f&^&9gIC2swWrDITRQMs5WQDxFnQEp0d^5A@Qe$k94^Q+SE!!)i z6h5oh480(DY||Q;E^)++y71g+y6tS*gOxOE@=~RmHZgJ6ODLrXxfBYwLeVbwbc0-q zO{CZ11wBV>Eal*d?Q%k!=|o`*wHPF5!z@a)+%aoY3B5x9vvJ^ zgJK}msmRrI6kQ(!#)JAYqvg_+3iJbx z;L^Uz??B-YdB-opPHk)-4=HtiICaTbwkp@x)HK=9B*D#n=qwpj`n0b&3rnDaL;@%W z%$)0kCg5M?Lthh0nk%i{rda24!)UBBR#%tCjtNJNMvJ9iPNW@V6E-G{d}*D#GfKPJ zRGJLd;&v^CW7VGC?8Ty7HfWr;I(gr}x4(8ms4UIHLcX8L22GiodF>_QT7hTWREG9J z_EJu+7itm>G=!85N0SC$*1ZwZjxvJ{%d=MW028LKcNVIy^rXB9N@m)S6{lEx3cECo zA=_IzvEqn;YE!}(0@4v=dgm(FfG?A@!EQnv(1VPM37ydcyRFqsGl`U8uO=6v}Z4jxrO6t}ST(9AXa@m}k(|mzQus9d5bams<_H5h&6>P{F)mqg> zW;&3q?RK;c7K6EtU7tc=t)Gvj@KxK~W@@FTodkT49A-JYRih-(F(Y;oH*;~E zDJvM=h?p;X<&c$aCn`vjiWaBUpi1t{h$U8&@yn_^Q^OpHau74+4g<^2yn@sVrdkWi zM_EA8A#Wgu4gpgHV900I&04+_v0H4K$QaNsGh~(awCtTsRHm%&B%1pJIHbIBQeIL7 z<=vodW|@i0wVc+~;p7TP?HWZ_aJMAz?gdRId=l&R;t|y4Lnl>0C<3*V1z8wv1Mt<3 zqSakAD>GWXw+)shQ7=(f)`VVnE(a+LIcfxNpAlqRjEqvzDr|-(!p)qeX?KyQyZvDm zb*ZUQFoZ#+wyN-C-4WPN`s4bfCZW-Ud!dxcUoCX@rXi96XOprqWY{t{&jP-nKBNV& zE1?1aP^iZyoe7<@BCZpgrE#}WE`!n|0?3ankMOt>I#7g*jHT3~<{ya4MeS5CFZPzJ1`}bf6}na#}?c>H@|nK9=}zG z!_8almMJ=%toQ}IHU*dvBuwyqA23BH+=}1#0zL=8j{ab^WyLW#d72mdtHGAtLRhwT zZ*7^Fb!YX?8GKV>%Jt!&!p@6I4otGTt1VM`*n>}7Y$UeZ>)pNYMq(YA!HKvdm>e-S z&6bM2C|_^y|JmOH`&;1uuPp$fomQr_8ldLDPhe?tw4CmURRK|A|Nr*4!Tx@*e;Di! z1^Xl6{z$k#67G+L`y=80NVq=|?vI4~BjNr?xIYr^kA(Xp;r>YY|Gh|vD+c6r2OOwa zBtVl*Z#S~x{}=gD(NODjn(gfWR8qlTTT+o0y`8cM25(}ULP2A?M| zICAq2e9ONpDe0j&ezlMi5JhRVDV@Ap{ISESUoHLwwl*aoX3JE}C;ojvcQus6y>F=p ztJQ4jG?}^sZYVDK?s_p?z0=s0rW!cqmMQqZ-RZt7hT@Kq0F|CCv)yR7+U)q2)j}Ex z+)8d)2%~N1o&U#IOuY7rH@CCgw8^H$^INvf?d1Pk8=Hwu>p{b2_}-8UN+-joH_rig z;6~7KwQrF&vymhn_T?6|dub-EZY zbVvbnILuqNeSPB91MIwjS-6f3JZNxCz&bcb;EoPv0Y4?Xxg%J&td7mqZJzJudE})P z6yD&SO<}FD;KZHV*!k1mvKj>?w#|oM?F^f{ezh|M_cfIf8Vmlj5=Iiox6D?f+2YvT z(7tNle=F|o+}X~l&Ht|KSx6kN{yX54LeBkCIuKAVh?=NIJ7k>Wdq0fBky@XZzW$&!t z9{l0%?eEAQr+@$Hv%6D=b8<4_F8Gx0>#1|DzS1B1+)dZ~WqRQ^-*7`x_Va5V`|54U$EMF;qS~E)yjcI~ zd*1fk)z5vX`{lgitsk3Q`nC40)>r=e?oWK>m%EPp&{YpAsE@?H{E5Gw^Td6`G54PN ztBX~MCw%80`0`6H9oBsMZP$JLvL8L;{pI)mYU2O->3`f-x%05^inQm0#!tHLezg3x zQ;vA~FvtBLgX-!3TFu?G@2k)7HI-X@)8}CnL{47Cn4NBcTlT)Q)2ny7rP;oP@ASBx zE<$lDDCde>`2SsxXJ6BOcD~}(ub%b6SE1k886W#jw6A!aj|I2>+hLpY?>p?ZXWLxf z|M2$a0$=@-Ah+aqF5i@l=SwuD3uPE4STSRp?zvd5Lg3)NWhdYjj9dt`-NkUWk``O| zrsNHck>>V&#tgUqUjcK|gP{ZO9V0vE-wdFg((`f)BF4TUwr|wzob1&r0*Lu0fI!@a zFwf2QmAd!7gp1ik47Dj_;!IuB&@?eEOsl<9z|K(y^X4$9miL!in_u9f_l-gW1TljJ z8rTeRVT&L))5zwUHXnh3$Zx^W+_|I88(D4&h4cTdMG`A+GVBJ#K71S+h*|N|5{zfK z@&CHf*R+wTvhU(;IQiPg^4h8Ge~bb9D*6QBfPugFg?YIH@}&_G87J+m`Dt- zJpY2HF8JW7fBomFFT6az?0NU&m%ihgcU<$G2Y>nW!}?>szPhP*_4y_05cIZ>9&yJ} z#~iQG>v=KvtfANtyiu%fv=Vp%n z({>0ffOl!d*{-vX+4Z{P5BZOC4m@L5bBp?&N%lI$?vEUF^X*q3@W)^5dINgkAJ4q^ zxr6`kyZ4>>vxjf`<<)=LV>!o zzC1ns(1(uu$2*q4_`80W$MuOHz1_%OdG41FJK>N+esRLDu)~l4_2TuvJL@ZFeRdb| z+efYtDc;evb$+sSR z!>%`<@Rr^86{z2y^w-tz4?g|42fufA`^F=lQ@-s_N1t`;ttH~UkKXy719n>;-t|o8 zywgs4;=sVImp0fN+4eIn_PKv0m)HN~xx=3XhoG4Hwx1sIO8MOj?D*ps*FWEMpZ4~z z9(3Rz&hWqTo@x8=@g=QmDmOcxeL~*8N2K_hd{sjEuhS+sQ03Ei9eeokhweV`@_Tk& zrMlz8kMEIfH?#LWn!V5TeCF-mf$GZF-}2b!-*DA2cOSa_{H?K@Pk-byyUG{6`GLb; zy6nTJ$=`MC;q9Z|bK`+mUw#92@XNlRNq1A1_?|g;H-+U+*E|z_@7Ato_B{W>WiOt4 z&(-H2e&ZhBUmiW|$5(vz%I@XY-1+P~{`0h(dH(!Q@3MXT*u!tRQ@88j(@&Slb|Ku> zEssBX@z-zpick0Ac@_S>N4;+Ax!A3Q>#yaXJawA(j6dHS;12tj>*I$$7rm9*^>g2u z#6i1vA%y9^SL%QJUg%}hH(vSSbFUoxuhaHi`Kv?U|BCRHmm)8=j{J)J$fy3QdOUi& zcrEe#gPM-F)GKfdwcnKuRh z^zyMke)~x;wd9ArAGy31f8#^fjSs!$kOL2Vu2E*TCw`NreodoDTlmdw(- z>&@uySN42y_wEBu{`9{rFGXd<;r}}3bq74-e>u8!`L5Fs``g>86K*-^mBY^b`vLB& zUOD~DU2og-=rgU~2Y*3bb;+|Y8NPH$^X1#V_w;V{1FzqG>t$~V1@AcXPhURs#H_A2 z@Z4DX$DQ+&^4W7wzUd!F=*62)`u;I>;~jqxg!C7`Tl~c2FiqtlV)%XMWx|g?{PxQ~ z{^d7~zy0^~ZaU!&4^G~6P3Nv_&oh7Tt}Aj+eB!RN|A;M)y0G`AaO(;u_Vp)jKj)IC znWpU2^sdCx>5-2I7hv16O?Yrr%`FI#Kbdvu`~w^42}al-Om)(8a1^E4+DBStxd!PBO`-kT|?mFdLLj2)B)NgyK_=|%|CtVzBBz|}2 zx1RXj-=F>AQNnYFo33x&EPej`&t7x<53VGxaeYAb9j|cq@83DvedUvdZ$9zoM?d`g z;5Rj2&o-|5J#yhMKmYKv$6+^odihPB_FrB1_ZzN#;L3*h)3Y`2I{%Pso(z2Z zi=IjG*30_O-SIB<{Gs5x(GNZNr%N7s;HWi z`S%~fUi7}J-g?Q!{3U0eaQ|C+C*0Nj(W%&#?oZCYe%IGN_TpQ(YmcoG7hQJHiKl!^ z?Qc{cys&-h_{7vt4!CFh@dti^ew)&s=>KQq#rj2OUG|qlsJHEre)veZ|Dmbw>ie(Q zl4nlyoO0g_&rr@itH9-Vp7DL>yZ&&!@BRArl?QXb3f%nW>?z0<9q*A9RBc}VmtiE8z2Aj?k}=u{_LEu{O9ZLc*8I6emtAF>%#lf?{t3Q z*30uhcv}2`LpI)X?(RRn_`q?78$T~&kH5|Q)8GFrU;4u6V@FwnM`?fe;(gybef>}OUV2g_D*f}3-~Q;XW4?U0I{!!MnU8H7p1SCQ zzkT|&XRiPIrIB}!4_|y**510}$dkWGN;iG!qgw&n`+i|P>hKS?WoQ2(akR^I=P55E zW9bV$)tjESeZ%HI=T4%?UH#LO4|vn;GjI7n?~z^fotG9D|K$7EJ@vQWUhs}k=Dhy% z@pqYSS+Ng(>Gnqtyi2_JsNa7}pFHwopS$*jufWi1&`ID68-p8K)&`q9? zUwP9jy-)t=bng21y#K+6zJFWkp4PX|`^OvhyqG)hvYX#RKl9R!U%GbnALX~{Zu=J3 z)m&_O{KIFbe{q?#=SQ~YO_%)U@e@Z%{`}{W;p?B?_2IYX-}n6IZ~oZ)mzv)`G5_1M z!n4=><1Y{ICKA7Sqy2+tB)--T{q%i5n&+?k;RC;q+(=yb$YXl~AHDd&AHRh;;T;Fk zuP?ZdTTd^2xWkUG`Nj>QhrV@+N4Vmm2mbS)7vaA-@FT~3)pgR78PlUjU3Ld;-IYIo zW?5TOzuWVUvp-$^_7|`G^xI`ee)GfiN6&eu_2>_rc*2Rs zwa2$Q&feD*!UacPc;BTrK6lAoYUbP5-~X4#KT6&~|M|ApoqG9iD?jtMuGwg0E=j2CS|JC0Zzj3II zzy5p0zb@a?d*`b77!o5%MY@T=j^?tgdk z=|7zQ?B5q>{rt}#s{Z^-j*~z2V&8kUm%n~aSQjC zhYq*_|Kl+~ee}9)kav9NlTU`Pz5c0F>8Xaq!;O zL${}Y^Ub%O(0NyL{JAq+G zBhLP)LG@3xdFheEUFW~J_@(ER(Nkw=YqpbZpTDg6);D|ED+8Aw_(Of->R|ZkKb%5u zA1!^FIq>;!%k-nZ`y^bcd6Ucq3^&dP7kb6!9Xc z8^1!z;qbnGJb=KBo6K3cU8kl{GfW?SYyto4g2Ek(ZqK3az2m#9*9y!bs_wG%T31g% zq~$>d!7QFq>?hqVf>YErEa4svJbpy2?>ImYJ~uIb;`^CN(UopniupPQl+K0 zL(tFCY$O}cU6-SfhOQ*c5D$Ym&!c-7>9sk#Bm1L+kmn(?&Bo!rGyZnM zj6mA)55p`-O)jgDP_{Y4Y@JFUua2lTx@=qlA}U7C4@i3 zlU28#TJ|uaPjD128&!g1mqW(zQI&e^QPGam*Vj4N`L(J=l*0lAm->MA4DbFQLFi}@V)I0B@~ z@ti$~hEq7=Dm@UJ`sv5sIUI&-*k<1TSa518ky*DBD2eraL%+0SSdb%v;;6~qoKi@r zE=lY0s{W-O5vI;D<$dvob!oe4IiJk{MaAl9gPoKqQ{M&=*fu%(C>dW_nDUX$?&)^Z z9vi0>#?cs=dn=(Xtidnrazt&+5t4FOYP_nCP!Tg&xzEPu_~YF{8ptLMiLaLK7b*gi zngQ?H`ocni3Bpn$hnwSJ`pD-+325-V=Fhubc@aOI%6ozetN3(EjZqgp(ERed#r()V zzXQI+v$2)r$0*=A-!(VN*oZ0TUQJ)xgyg3qv*%6pT(s9=>DjU8U6*I~1suA^Wa@B| zg@yhQG@)D*&W6661{)$mBehIP-pM3gcB>!X%`0gNd%T~6#zQEUNOHg$bUa0u@8sFB z(_3Z8i@BEYQU3ch`OL3^_~fj%R1L4-R-HE}11Wkf6j6a0>%*B=!(}Qt`<|g&!dUhE z5a>qCju~F=A7bV`9y-vK^KY~l7+kmt{3FNM->SX7l%HTdFiT>=5%- zI7%*SLY7Z3pI+fW{_?Scwp`_K0kr_sVhEI+)Fsf{Pz^iVf= z2`}~j4s9aBp?;g>Tgn%FH|*RAsf^52yQ=HE9(EsP8_|8@2}xK1(4Vurqu-)t>tp#< zjw4#5#pE;dV|uT6YDZ@2UK3#n!$G!SQ6P$PJh3xrYEWC}5|cJLl;PDmJ&ZB-Q#KTqtWw)K`~1e0+~AUnEtM z@5Z!2X>f+)U2=NyZquGi%E00lxx#wMarb={Y~$-HAAp>fcRB~;BX%yTodh!{ED8LK3UE@sDM zrY*+i#o@M_!;@_(@5Fps-lv-(aI~h`Tb~FA6vjvNU zyLU{R6=#Yd!LkrOg%Vx|uo32&lTqJ})~bAenx|*WjFv9WWN-~x&Mll9x2j@Z1ml9Y zz8%?UxL}^oC93CbpXOt$=1fwi3 zHi?Zm0|@Z6Wei7FrWCH~O~(ZZlhLSYBr9Go(uZg8tRSa%jRH=>uz3G@Z`1w%`1K{lbhSW~a z;z{JdmmOt`8w*g1DP0G@gL>aWFPQ}t59PzXf#D217Q;Q}>H?CFuuKdU2AbmjV#k(w zu5SQ7R}OX*Hm^%I8whlul@4H;I|&S0iIcnsHo!8=X&k9hThPT?QurZSvlU>B=56`G zmR^w73$8)3a9TOSBy2c^f32$GhRUIT#^%_AcWp#iq%~ym7HsHD0=-Z)JuGC5 zO12`DjJ?Rd`$2s*DrHP`v&TjwWWw&EmW~C#wn)oaN%(_>L(!*76r9Om&p`OWlH7Y* zC@dD2_*tP~k>&bPD#rWsJRMK#=Wo42XndO-;*k~)MiE3^$Uzwv6xT}KjM0*yp_aAW zCk0RBt^QKLbc;{-gFE=5nSmdfGUZD=1xUA12BK5>LAbV<7ogu1-ZsM1^=(@w{Hr%S z6meV<(yH6oC=$SqcudX3!d%ZBdcU_9#|C?_z)L`~N2YjlhK0xQ2ydDFNYS&Cm~3!c zC{F>I+fg#Ss#rknj|10kkydbBN|8^GPxBSSc5oX8yN6FR`RQD=Tj0&f>KlDR^qf#q zvPxJ+G-w{Oc|X}Lo2tI)`yET!1wp_Qje1vO64d7htp#SgPwrdEeolhM0oc3hP;--n zf=0+qP&S(6Faj@8?N{iks%QUXh@)bq9J>OmjXkU8cVQCY=3llx0kwo!#YJ}6bgA%8 z$KQf+f90PaF;6%oO5QTYd~@;?$_f%}?i_}Lw7&+Is3q@0^B?1>Vmg;4aU~D_t`|Db zO4 zBN>4Vl^m1;Hb1A{siIO2F1|ba5KUL6s(6oA7(CcM~zIqzgdPU zGoN)0@`bvmQ0(G?PL$Q^nqY5yGV%kuyRU|l<8=2i?H4wsXSW_a$4?GoASuKutU>8AFk%)39kSaP z^|LN>Dpf{}G5MpfI8pMOjSjXagx8FF2=F&YhX_kJI$M=`FPHJh_2K=JhmzOvVO>L1 z#*&vC&LnBxpPx0^nr@pGh~z|5Zk>Ib-1Ew0TJn9{bvxg8m0!4)tpW&Y=YzPp?JVr{ z+GkQ*=%sGucCd?Cv>23+a;@K!wbLw&u3c?}B3BXq!9s%vd|S-wVrj7}d__=o3XD9l zmfEL0`Gd4>8QFH*IpKnexho;W6eRm7NeXCC{A!J4s5!aT;nj&Z%NRvU zxhp)H4Z@QA2iXNXh-8@cA1E)ld>*wE91E*YrpEK}xsmkD=>)R-oGJ9--&@@Y^a#{j zw%-8)Z;9UyPz9Z(JI^YAc7)}siJKXs!740SR$<5Gn%*#orP@xh-ekM9di8}g9}X3F zN39PcUZ~~hJH+5icMVD>w|~d1*mHSmT1oKD&aY`6h@^f3X+5tzeCt=No@!AdnOCf7 z5gJSJQHf}5sn%oo(GKL=;;Pn3+O_=jI0>B?tQ6`p;Lsz)?}gt#ErpoLmD~wumg~9o zre7jAk>>q#rjyosZ?QKg&e=Azsnl5tUA|iDaSg#ut&dSVG{Zq{IL=*^1fOK2FUg;2 zN9(juY>=~B%U8Ircp5|H`A8PQcG}N5;91++BQvY zVOvD`LnOq6_FVT>9H28sTc;eq1Y{A&Oe!-ZQN08^PG2xb(M=x(_51D;VaGRIte!o| zgzUJ6g!;vwUj4=2g+|w=P6cIl$uY+i+88G-KL5S^@eNP^MPL5IJv_){9*dU5{<#1{ z=fcWuSze)!S2!jM_R)S*UKz!Z%w=&*h6d%r=T2cnWi69;=pNF$LLy2j<9pE?;aF`SF!^wZU$Un(vjIGAl-{3vqO6~BL z8my?fuMAtG*>Z{5AyL; z_nXRm*Bo)^nZx#T2-C?2z>%|$5iHb@obY5TutrWLe4!2vrzYwlCO1h@`%Hl1__YOGT)sY<8XaezTF} zQ2vmv@XXnf28A4zGi9+pHNGu+m0WOcdbQc_#T*m$mAxl8u~r(Uc#GzWwu^`Yi)~T| zc!I8`j2<1@;>6^hHPW;YJS5=N16s78kyEel!CD!urx4eTZp(88Xo2Jz-ZICe_F~G? z`hdsOL#Lw6M$72MARoRRT|b?q-k{1Z1cJP_@|WamJn<__eWec$H|oKlz{s;nO?GIW zX87th;f*qvvroo~O;0#*BEE1_!@+kz6GLU~70oZBCJTiz)dHDX(|p#{MpV6ONwc`9 z3&I1(bOU8WN7uIaB4$d~Gs(XFI?9w{Vx{fRHdSgaGV_$66r-8|b9KGN?$dVHeK`XsT|M}fI?Tp~T*AwV zvj}vf#~2;H(?udAExH;VQ{qJ7^d~`%53;iq)_G?4r$@cjmXmN4v(nf0m$c5I@$y}4 zgESYo4S8{4z^k3Wc>3XzxlMf0@<~O~@3lo!MP0>VdDYh7G&*B>TM}2hp z$Myt!tqwZtnD-$fO5JG8RoiRI&O6a*#im)Cm{ai(imb!C%gj-^SPUv!DoF0u*G5D^ z0=OT%F62Iod7ED6H4o?2#+LvKTFIed#s|UjC1GXek@v`<IYW*#jj%KD&S-f(Qg}yQnt>hc-$ssAHaf7?6M&oXKemc7zB^yPi<;;8c+$HyM9aPEoeqBC~ z*Hy^12bnwY(5v9s#}&gA+MTO+a3*NQj(;N*zjyfm*wEMd?Wx%D**+B(3qI?o6Jo<> zWniHF16(oDGW}W3#6(Z~2`3mhXqoZZSea-!@LAaZDEZu<=V1F(Gk-qzw?_T|TK_2d zef=*D{;vMF#y_innlSdy=Kk#LcbA{>i?odROw6AQ=X1}W{c{k1zy2gT^ems<{_W>y zZ5BqBPpt6AXqnmnXy$L%EWgLk#Kii$flmhY`w2#tKdLga|55r!)!$S4TOm6u>*uup zto$hu{=E8Kjp_Gjm>K_gG0gPLpCi-xyCq`zE7STfmWYv+_7gY#KTsmJPs;I6O2qOf z()tH<`yWvv9=U#cn2i@I?p_3=gL)0QKq2eoR)S(ePqIewn#%_V$;{%)eu)Szp0%l| zT1ZcPb~WeSF+)i7r5AVMz{);T4`zIk1Q`Xd!SNm@X z(PuWre2+ z4)sqw^l$Ite+Li!?&$CH{Ev8uh4B+W{Syx{eS)RG-F@OAmd|GXy!&@N8{TnX zxHmGvehVm(mcBTMvX5@^f;|0Lt6Zq%UCi<*VSoniZGbp>0Af;5q*NdvKtcYzUwZt+ zl2Ca-_kkdsU2-w-#0oFvd0U+&>5A}gxhI+6AM(3T&C!x*b zURqiE<1T|fKiC5aXZgR#(m(JC4vK@r+tEWoHM@2jAdvUIagHDxz>5G@O@X6Z9Ru9z z6}lU>VR7V=qDs0QetzNNo=B^785@us`B` zm%n~9*EJ{JDd?vja$WM@ng{~4j0^?1yo>geJ%khJ(dJnPT{pPYQ-XS@bm}ly3GQH= z9RQ8Iv?qIB$c2Id4bisSRJ**O@(t$qB%1zMhNBPe=&M#Z<REiE3+Eww_M z;o}u61?NYB0FeY30FY+`memseL>8zUG5}2lYxWj%oS1Y9I-u6T({%8v4$M@%f*#WEx;_O8b{!s366~cM1sn+lhd;*{jSRw`i^zZWZ z>(bD>8OM$Ae|ZP~@Rj-$w%nd<+o|06>seM8c;91bO^u zO8`W_HzWIquZXbc2O|D3fAm!HV>!DcLvH>+fr9_J)p#4X1QQC>{6Wx;Dvtuu;_Lsu z)$oyh?N|Gzmiz&}{n19oBFFy97WhIp{2}PCqi?^($FPKbf7`qmM0>4?8vYTQA@C7b zi-ctRvBe+*f7wWhf*5nzr3UGw7WBk_2CI*YP{JpE6Snm$_18~&ka>V3xR<+hz{?>2 z?vF^F6PWA6XQdtAiSaFh!^Y@$pF&EYz=n?^HUb3(0Q3-`7bG3M+eQ*7{-CN53;4UI z2^0T30H7m>Y@lY`NxYx_!iaCJDh>qxd4qIIcF-UwU=W<|VH7|J&d=~lDEJUBHt!YS z;bc%Ql_OiyEw-0*FAv}VP;ikoL2uBD9t#K)MERoThuN8fak6cH5xDZ$HXlv`2 zhYC19bR?N4M{*Wu)iRI3%2wEa98t8q4QJ=_@>kE-vpz;*-_(~^a@DO4$m!r1`H2hW z($~txvlyVJ6;alT^N8K)W6+Dm|JX|U;daWfPwX|WvUJBV@yb7PbhRli0ix zO?BRaP7FOfl*$n=DJo*GCbuf#@2g$szF9>{|c49V{ zZ7qTpewyCr_I@kiCzCeo;}WW^GRyS2Dlr7yLXa*gQpu?<`mt*R7`uk&89TSMXV{W9 z1k~xVC>q7c<(;*KTiH&vL$zw&Xf~B!=9pEJ;4?fQMUvW^Eb&(`ACP07iJIe-EWI-O z6^oP+c9AEhYCQv>4)|39yEW*b3n6#&sX1yBWldVeuA|FNhf`ldeKY2 zavn$q2ZhcL9f4}l%KeIAT2ZM)HeR1_`g7_u909p1>oGJe(#bWq6bsbOc5wY|)y2*v7yGdM5-lhkbJkGD)5z`?OOnk}%^#)28pOm7% zg?+!!6Gd%PnqE@%u8#4opO(G#sAJ9K>nrWm>rglzdOykbdYdlCV&W*a3hB~JoMcON z&hb;iiCU#)tH|vP4oFIZ4>Qu$-GaxpE1$Eq7RrEDLupi=3{jt(mgINj$x-`BDuSj^ zIhsTE2yiKwEIA!F5;8lG1c(Dfk$K@%3z#2|BYde2GE?3pjL)*BJ;g0dFfHQ9%n3T6 z?J5e*ic1n%)XB1EiIBk}^~gi;g(6w+_}&jcgwFp(%hSd!&8tS9f!RitQ^0q1L;GV^mG7VLp8sYEXKI zU+Nf_ca*i<6;m%nz86 zZ~WL?Ib;ttYiHAEk&ybZ$ZZl3MY-lQ7_um!x!!w}Amo9@2Zk(}Evggob&o(#Bg>M( zc_bYP^jd#XT#a>69$oheM~aH*d3OOy>l?m9Qn`0=)P|$j5e%8^rq*>X3-nT`s$#aP zsO^{yUg*b*$+NoktxuyPG7Kmtyr4;4abc5-W?0oV*;(RHYKB(>zJ< z&NG<=wU!^76$%{SVUgZ)u`rQQx8nObYGzXBnJJNQ(aniMx|=~HG^cI480|rN*S8H4 z`9~G@_To7LwD+r}?<%BHY6H_cEpY0UfVKwmfr=~(TIrF@;?Uug{uhf!pK+1OD`<0V zN~9WhkP5k@x8aTa9BE0M!s|H^bd^(&ix@OD(`>87fOCzs;oJ33x1?8X)a>zfz?D8= zz-FYd5MMUCYsF#>=bTj~jI8|HJ0QyBj_x6`zOA|MkzT!JqGQgb>roe?gw+^nIR5-r z2&u_~0C|Mv#%{I0%1qa!+jb>c>7%OVR?b#F51`9HCL_fXA=ELv*0u_TW7V2QbSS&& zN1Vzx^Zi@n=O5rKde4YK8PZg&1$0k(qf5`IMtp)BDX&RjZKN?O#NgV_`Qt)t0)-Fp zqS!HY1$QUvz`N5GP$iEtyJdZd+uo#Yj_kF=?0Fr7ow2nS@O2B0R^B;#S0bjmd?G4G zf)yGS16UpUCwXe^k2Cp_5KNli=E71LVajUGbafjx=qO)@OK3e2u* zbwHm2$u*2HHV=i`jaMgQQX@A-45Tp1A_rL&V=gPBt?$)is`@vYM1&KT^n?*F0tYMS%*LfYl zB3Vy1Xxv7s;vKl?DVS=7ckdgz4JOnxw@HWBN@efgqiCY)<7{@nV=%SP_rl#yil5!H zx1e{DtY7B6xQrL-x@saup+28%NQuxf`1z6LB7-fVs-@mi1eyc6Jr>L6T=_8ZPdTDi z)Tc!LP_nPd zn9L0&>dRaD!>v(eOxv>r8z%0LGS+U;0#;byL>*0-2<@`Dq}<)UdjkEHPTA|4Zdj{n zv~ajoLYxx(OTz6JlvkoXq<+NQ7(xRBLp@K6pP4iRIB#JnsF*>Hz&ReLYf(pN*$wG} zwYAC<7nwa+%IJw}Y|YH(?&Qlc<>lPWdpGbc39?N%U27yr0d<%WB6vow!$bGbD>a># zfeCG9J$(6^sUWF`KQ^*Jqln}zhhS7x&I{a0MI$hl)j5PNT%c8<^d{7oVJP<8QN7n7 z)L5BgtfqhxX!+Q~r{A>$KH_IabSw7;$c{EUC-Xac=-T3|+z2#xM@;q(Z`- zXT}5Ec}N1@zaJ+CH2eSqf9}l-M@iTw*&H7K+*RSC&>LW>t?M84;^0^l4N;VkK&RZ` zNXjgP$>M3z(QCa?ZX~r0c+l0O>EpFH=uJh&raVnOMIQFYntsVRO@EPM|9(icKo6zo ziYE|*zrwlFY=;5W%EkA?M-cM$TNIzOBvfMCO{S@SMS3M7zt8NdmzWrOg^PF?j3r9& zkn3($Fa)Yq)0d@Pon2n@&PyU0Za4eoCKl*DiF{TQUoz1f^^W94-|p?uz5Wh(QK+MU zft=O{a#dzGyUyz_0>RalMb{_q3z5i=j$1PHOlhGU#qpN&;P6B4LPAO-^p7zx*`rRY z{Z`%G2sTv0fyrZsPV=F2@zFt#VK>=O0QVGV)p&}Jlft7s)UW9KzD5o!na*Y8oXK>G^7}Ep7NNjjaR2YKaLE3L>yd| zP7MpddSt3Ut7U=coUg%OBh3P}P16ls2~RPW(NY`M&*oap% zO|gPY<*US|Pu||uq=_h^nGu>cv~4YNt~V;QmN&dErM61-sG-Py1H&Wj#+fE)FnBC*ozsUq|JoqTa}L` zzXC+-8lk+AMbP71vS{Yx5I*!kqiHg?Hj1m=I$%2>CI{{QPJ$si)Yc5-JCy$*LVm;!0zG|%@$v+zP4V!k0I`j zXJz{~$EzPMf-dW)-a{u(LbclGlYYg)B)-Q^As1ekS2L`60=&lTWNqX+3Dai@>;U_- z_hq&?cF`-P1v*ex?@;cDy^>am1`$CQOWS8vZtO(y_OFa{DFzA z0a%BI&u44*t07p9k$+!RlK(JpVy55mP=^Yf^_k7sioCRF3H}G1<((sWn{#UQFO8Y{V=FsuxFXd86asW|OiHuv z#;TlT>NHn(=<75dZ(bl{_2=WB=Bd){N1l|t#A_NhXf?4_Km8e-TPCoWcQIk^`;;z^baHK}sBp}t--obqcG6nF;}e}gsiiu>}Tgm_(& zsTGYQT)i`cbc=>wmotByG||o~E}bf62gLI$EO*1cH;Hfl(Oh5qtR>A8!(8A7U*SE1 zkKmEAQ5^50pM4@}1i0kc;~lN{xFom2j2IkZ4j0torY zU&b(nXvz6Mp5w8J8R+Cw$65JTxo(Q0B%ZeC2}WFX4Z>O=X?@p&Mmm}$Zg?-oiWg-# zjz9==xo>zpnpDz63>|~KhToRx&jpND5)#AqA1Sp?+h+T{_0}ZlOsbiMX(Rfx#6twK zy=UO2F|5r(#)eu04M3)R&8>O9=cBAwpr8A2N8ehOW){W3cCqKG!xxHueK5oH8IRs^LWw- zYl_phPX={|vnnK|jj@JA(2bl8LuUxZx-uYPib|>V(mSWsjl<9&U6s}F#dABxdun0< zN6j$=V*(ha-|h0YPE!)U=X|JcKEytGcU`<`w*fgT?@Nh@A44vyAK7<|2x4|DOsaqk2q40bDZei!H~wLj)7 z+9!`IY+72*J4J1z$%znYV!~yW9JpPki(aU0{D?v)BYUr@cEt0Yr>@K^T(mEt;r8c|9W)+P&@J?v2vxUuEV>?};P|^{$}u&jg3nyrkC-zpiof-90dW z_hgG`@+yX<87;S5Uv|y0!0!vj6SvE`d0+vr6s2l-LwMzG+0^Thy-b1;66HVRK*_>y zvCz;scdd0y+d`q5R1GI&u+&1xOTsbE7CwLvZ3zj!>Qci*-b03rKbZF>fl_N(GTzJ; zcrQh%b3j}iAFa;x5b=~Vc#0kYDk9(~**m^hpCCN=MZgtn-~IfcEv5WXf%5c#Vc}Fl zm>i|3d)qM<2dV)ox`3Cfc%Wl!)L(b^02L|LiRM#Jo-XT4d`L<`RZEwnVRE<^uA&pd zhI@`XQ@1%NavMSL5~KLh{y0FLHS3reo0P^)u|@5Yxk~;DnO#IV_l0Hyn$L#NaX%Nu zVAPhHINq>NH~I+W=x+3sPFa%&^NfkKv3+bQx*?m?A1^|Dsth8tGlMBSL9^pt(A1LH zVn&#gZl~5bkD^T`%);H20{Fq2+7k!(2a-!dOtxLM5JQ!sJ*R$2tVbW({=0|E9OX9W zcP?kZrcaW#V}wfER$#gKU8G4e#y!tmG|(&tYTtC9`Ds(Oh_A&P`{JAjN*0-icP{+N zovXU2oo9G;n!It3Aq8jOKKwzZTL*jXIG|CoQx&9AxBMLdO*z?QE&z* z)w+UBm4U+H)a7-$`eOqsm4QnYJo^fUqkna{8&_Oc5OaFdsd3SSlB;Vw^0Mp2o{#d5 z&F2kcg=70dqD81xvWA_9YR4KNBnWsg_UJ~2VIqJDeW!m{Q1T{y!Z@c$y!<-H9`Y?3 ztzA_15_5pG1jbki?i`IJVe50u{i8 z4bz+WcTu`nycgxuHR@A~n9^yHi|a1-C&xrLjlYs3E-#Ma8FJQ_ZFK$KLeH}cPc0uU zSK}X)E|82p4=br*(<})s0vu$IR4ZiizcpQct=mLRsBEd|-ag9w0p)p!8Dgp$K;TBSWS9L+H}7IEUg=G&q3@i{#H7D|+{}@NQD9!>R0KL2$9z6c-3!!VKAxtt$*s^O zl-F;X@3(81H3|D*L@UQg>q~dnxf&_^wytt%(M$8F~z>+r;pZAwFJ|_){`lhm!7nAYxs10Xh|(Gr7lTasQiK& zHELOiWz#yYfP9YeTf4BhOqpwPRQ$Tf4@r*H%K;ddqTL?oCyPdOKj-Zy_ln_(Qa^gh ztXvPvFRvoq+|LW;jBr~Zz1Kpo)TDK`zTbDov}YHnhb1G>4Work6Bk^^jY# z+3sRji*Q74n-D)VlY z?W7Dl52qlRHdn9&b3||^D9V&VZlKgl*EhAevUPl_?S(++C|uXuqpxJM=N&m>lJ0qV zO&>jbliOvV6F>(?XRE zk_WlFa=P1g9_UdL?8$OEL0QUn{o6r*3HUbDHe{$AUceqDI`?2I}();sJ z9;f<>^xVWT0>f{f4#IVZaA#kKWVcfym@5}bbwqxQr)l!8v$JyhQ9s+cT{_^xh>k?P zKQ6}D!r60Lms$M8C=i_l@8Gqfpn`Tv;KM=5LzBC1^DfvnI2_8lD23UwW%{Xfyf z%DOB`ahtare}A+1As`uI^=;oh92!r70!ir|UFC#tx5PwmUeI2;BL;Lf;?&4D7MP9q z-s@bumF?nYxkaOd8v_lfjO&xf z55oRuP7a5Q!k|>Sv?{>5eV*0_@SXfH2JpM9nf&P&Og_vq2j9+MK;T8R>L0bi`pCC1 zXu%vK*9h?9I2h$J+!}YB>j%Qv8O3%t;I!w$y$CtIeNUJH{h zApEYB5uHDMD-r^59IE}AU+n=-hd{f0^A0wHI<)q5KYA!E5D=8zXQ4GS+a8}LJ}JPr ztECXN7b|k3n?X6PfhhbOTmjH&!Z(UcncLUyN8k1?e7i5h0#__UtIEX$Xmk^ZZE*UA zz0MUp)9P-JagCbM9G9w*(a>3?VkVJ9-D?=8)zYrZ>Vk!*h`BmEzD;wrdo$*CBw|DD zvrqgJrZ1{V3Fpkm-8m`(G|!Z8Xoyt9EQSU0@DaUzGZEPvY zSwVASp!i0b{zGnoH!-#HcC2N}s_+6YZfzE58jrg0_^a1Ja#K_q1r$F9#RkH`Ws{8$NxtoA&-|6HmA;77WR7UTJ z!Hrjqn~7gS)i+`jd26!iyJhw+7P)5)4OD{(Fm7~}_L_7$kW->39&}RB?F|w~k=2`7 zCeHFs?Vc!E7yPxkr}F6j5N95)0W)&lM3BKoMN4yr5f#<$GN}*t zTunyUw#i}CVvzz2bq8i)PfuuB@t{!DgxH9f_fyOKb~67b5A&Zf z%%?i}n~nKDd6@qThWSIS{09&7pD@g4)&JhZ`~$PF{xv1(zhIU>vYObK|6eo<`)9_; zKQ+r|Ov&#AkADz?|B+@Hv+ScsXn&#FU&d`%=v&;y6bBB$g=tOB$zr8h4gr(&>#`zA zNru3TYcy?O;+n1M2SjQU-3*U(g9NP<_X3y7wqcN-U}HB4OIi|V>~VV>fO)Vu7*KZX zWXULC6~MxV|G*{}leDaE4z10;O`8sf!ZxFiiyvHpfpR75fX1Y8(RI{Dlo<^9Nl(uZ zWGFBM&0OyU8ZY(vMQBW@sc)BRi6>Bv$f_0Gv7k-#6=Q00{gQZSOO&|I^|xk674kWp zv;(|ZE>33YgFYNSdmr@MF0X8N?+CnU4AWEtDdM$nT@SD#NIqhzEsA)1FIz_SpEAN{VPWLi)i_K%+o(b3)`Pk zP@Kla zwnbg5i-(OYYp_8e$$N6HG)$-nL;&c(!T>jyuV0fVy7BQLhzTKpAc5IAE_xUsmbVZA zN+4m*;KT@uJ_1^zLIvp6cu^ttp~|BKff~6H_-h~#sA*wS;=ls@d-3Du{rt%rnB_rt zAb)@#Ljg6)i>Gn<-_VXOfWx{u_i9((=IY_YC!s;4BqaR&$c9sP49MS>hd?TY?qToI zu2KyaMBq0!2Ik;h(7Gjnb)crd<4` zjT{XP9cdTt@e{RR!v^V|wZ~5DiJe9Zdl1z6t-;wB{tdNghny2_+v3v8B=ae#(ROd~ zilKv%g8>B)3+(WxVIl0px*$C_4)frtEnnb2roLFh@qMQnMLo1=NEdlVZN(Y#=Ll>+3JvTqYGWWU^ zgq1<9o^vrzFO>Ej$1>PlbJ^5FTI(9fX1PGZX+W?*%_&SBt)f}82MWKVB+ETJJkKgL zH?oa^n7)S5UfqWnkS`r|W-JRwO3swtPslOrxYm{{>R&BWqNrN?ffoDZEuK1Zw}>Xi zYU_uv@~(4S*bIhGU)sE7p#((mbtT-=#`ZaIb;U?g@V^ zb+K0p`^211wJRpojl550)^uGHAI32$`Qr=Pc}M?DlpT55q+|Yd6I5VJaxNqOc@>4g z#cY56I{J9R@%Mg4B&GWxl60%t0o*CwBc;N|)tlA#cI(T_AP zQJp)O_H4e=Lct)3`VoxH*t)OUrByWv;xJ7})``$Jkw{86xc@@_LfEt7T4;;XkP|sM z)Dy|O>1MtcA^4?Ew9zK)UEdtBDV$II%+sfsHqY(3`zpR;$f!Kg%r3sw9%CQ8{E^lA zc;Z4SmwVjLS9#M-rbn4vQ1(#CKqXXo1p!u~@825>SMJQ#W zm7aass~h~Cby@Nl7|RCt^RTI)RGBR12%6n!J<4f}i}gt|@o{76jeetEm;FLlLktH^ zKNnTVDkhlvv;)#ZJF~m{PAd(I^G2>PF*JfMqDJXe(WcsV@^>r8W3HJ_xjH6R7n=N1 zjg0QhtluA18yOn=lvQ)fdKX|ls)+H_A zZXRq6Y!~P9nOxt=k6(i{Z`)21A75-{BhVXY*r6FRv(-B&L&lvI`BB(E&z&5w>Y*62 zHI*~Tz!4dXX>#dT1gGwl5y8(~B)IF5u3~lK>NphrIApszI-~tdmw#k+kO-F>*!KPB+-eESbaP_>sFxVfu;uy$@>Gh_;^K~ z=8$5_oxT$cIG)w)RYucUx8fzPF>`U3++1P9Hnu?UfzU4RK za@x}9%oi|A&TsH92^?d2=DByALZ)RwqS{1dn)lN(5LG<25{xDs7BZhnkN|opXBKAh zJ<^ME9M%fdL|U8`iMHQTiC8QeJRg8(CTIf3Q1v1_rvF8 znr;u|V0ka7O=gro6GEs0r4@0^OM2c(a9kYL(K!ndl4f4>br+}Ujk~Jk0udmO7PU2W zA_ovT>`^bRsWY)P)<1O!aILo?5KRP z^?)fJznD{|a!L2NsmEOC%R`u%_+=I_Gev*JJ$Gzvj-VhfIHi7kdlMQDgf?F}n%DE#d}`u>0A{PN&56SArNCi;+n*)fmy$ z+HnqhrCAW#vj{LZIQFbXhwAhLEjm5Fl11jK3a)iT1$}?=oTU6B%-({Wy%Tjy|GT9e zIi#k63(he!_{B2wvlvG-M3Kpv<`L8DJS)NbL72_z`u+aGNj8%-9irNH6cNl5DLU8@ zWWd(W6wkKXgOCLg0u8y`941uM$KZRAzDXH$-IAhI>?BBzOfqd1)rq}EGP;#UsC1}Qla|Ur`M5(|PZ)Y)~X>97~2HO@=)qUL>(-Ne+ zR;ewHKn_-cw0_&ooPWjP36FqHyl7WbC1pI4y4lt{HYYEi9+PqDz`DK1F8ltl2UfF1 zbuZ;e*-P+5NgMp;9qSXq=8oS-4++<49NPPVvhQr}h_3pMS#$fcA7hg~`Nry12LT;c z)Ll76OmDo~u47Pm?prZFo|_+u1h%&Wkqq3_UVhpabUze#`=GpVrimeuwdL(CvnC2x z5m7-q+#47hcpA!*`C;4RYu9ZM<6MY3mA1@ctMSo9YPAPJyBD%Ow#0EVK6}lCQ1)S4 z8%3VT@vfO1`MvRQnlWyhy0JCULjM`v2@EXF!WnPoeF-^GQ@2u5R^W^mgWLxngs3cB z10;f&{js!L~dCBd3KW;TH9mqEGeMM5xuq2LxyG zHc++&GA&QxKpn6_Lrx7bA&-^n0sA?eD-wLNy&=f75ps{ zQ&qKn@QN7<6(1(cV(0SP8O2?%ow1Js z`DjK^V06*5F!d1&3rD1R%Fd2du_fM=jZzKF!!y1^YBbDT_!@3Kb6g@=Ybq)AftZVG zrSUkRXmDQdj(JhRW&d2Y8nPq@mFIX(0N2l6$$pYo9@VMQ+uAh*@lj;Y$08P)$OVI5 zyjzJ$i!9KMoW<$d%jm;A(FseD9O~hER0k@Ve2HqdmfI%wi}Y1Jo!N0}KyPrl8I%o3 zJ@gg1U7F~RcXhX`@bff~XP!**QnwR#-`fSNi<-T*GuNFyO(hKsM5uhMM0q|X;iaq= zXW5~l;F!ikMN1Dx7tQx1O3~@3UT~7unk?nG%oQM8P~#p!w^(~kt8zzg8y-H#jNS8( zH+g@~V@+cFX`90#xjJy=9p~C|sv6-r_m0q}_`MdM-EcDWJGDe0Vu24w%W%>i%x5=s zk581cOOx0wiF6C#O111wtOuKop@kR4vcui8Y8LE|$r*v=r#QxH4C1fl3yq-n`h~@c z`{UflJm4F}hHemx%Pbk$=!r$&(s-KcJ2+gbKdjQbqb93y(3({OGZ_+`KSqH-V{rK6 zG{SQsxS_P4+$1DkaPatg*CnW<#Z{ANp4pE#Cc;N>C(yBIaq;q)%_2#VPN#>bkBcnY z^i}+T5&G6504=+MzlmT>tu$9p=W&m+-kYkKx&VG#6H0h+7Xc17y3@DhMl1hS9#jc_ zyC#s*!TzeOzN0#lUUW9!pF@~oEi@`+CK#gV>g$_%Clu)!{j{1)%s{$a4t&&q z_+fh0nmu2tA`rV_OA??KMx;!WlNYXaqIBNIoA3(MTJss6oK9MutRCox=j<~qjB{&v z;+52wV~w_beQLUF0HcA79i^8zUyh#%B+*{%PvTdW1mT8ok>u%g&dIG?-o`vh4j@nm zC2`+XA34E4(;w}kNanz68=A05y^p_La!7fCy`1hqq|;UnAxJT7taLlgJ|HL@o=;b# z>bvWH-}WxOdTs$KyK7Ud_rf`$zg$ne(LbR<4IXIJCEm3Hce|Mw@HHEym3~8Z}){HN+h-!M-ZxT5<#| z0Z8Z}TXA}#5mDNW(kNc{RrS8DZwj{-u)6gY;iF^{s)eujpEK%(=g~w9J9hhKBle9D z%w?XFjTxElCo=HaJ+Di_-QeaPyx)1$U(v9>F$ki9kJSbjeN(q~)78RA#G4b(?Uv52 zj1KSPAbXVF?6HeLUjei0q0HolCAy!3X+$@;#&CUaw)+3dD_7UQx1V}q565n^Q<0p zaArbTIkw%EdD+L`B_FkvB_DQ!pA9ukBAvydh<(vz6$@G4z@0XN zp4hEaq@gp>f_7bY{B&FGzb98JAA)6?0oCa|Ek(yZ4Xx#eUCixY$M$|k7UqsW-v%Gg zMX2{$8keV7e-?hfoVsy#OB(4lZ*%cFvp!k%LlxArE*euEHA$(d+KPLYFDN`^xGWRe zaSohTn2Hr2R{CAJV`3#DL`-4~lDx+?M88+T$o?8K-3d*J{yc9!)pBE{30G?#*yj~IL^6M{#o;EoD(;%5}J8H{RQSip+& zwN~+q)eLdDb@$QS+Gc7s}h#aEesy^B(P!MIwY$q}NDH zlFDRFY{N96DJq}4h_K?*Y6k!3nIG-sR7%7$5qpchYMx1`AB@t6F|gO{B;N3FBH%^e zZd5aydP5qObxA9xpg83s8r^QV9Omm<#8bu4#;sZg8@%SwkYD;~m%RXZi(A7;)cuB- zNKau@_}1>YzPxQy=hdkkSRxh@-1mN{9YzpPD8&;X+AfBuOM)ek&o6= zRqT;=4-1;t{0^%)w4qM&AU18S!zOgOaMwqcQZ|Kx#348lH8fpwhv<;5lM&Lk*<$CL z{SmUFltF&jOg4l+Ac3`{sB4I)NSZ*1U7Phk?|X{Kgu)c4w_if3ih8M}HpVp6xiCPo z=!GWs4^De+)kRmZzMJDmLt2MP%3OpmVQ;Ew)5(Fq8dFed&^Io?S%%n0z^&mjz1GQ$ zOL!Cw!arE)zav{G+>;#j0g3EW5XB5}`2NF}f~HPI+>1E5Gov2eLbWX4;-(Ky)ZF@G zQ@wNCgX-mQ@Kb%`C^ka5^?Z5T8{eI{gf*olRnGa|>2j%K0&kBGK$*++lTvH<7GQWYt>zJ& zHe_@kzq)VZq_Ly+aplI1_V^Y_T1WcV<;j$=TD<)T*cmYZn(tu&;4SM3Pd9p({H+a) z>)Qk4tPg`4oZ1NR&7o7V!KBrFR9XWu5DM*-S)&TDlVzPZXyR@Ow^tyP++|DZBr^6- z)5yDq-`Xf2RRerWsnWCoY-=yPi zt}UN!<>h)5AlY5n*S^EJPilP6GBYEJr$ZmqoTQuVJ?~&ssxp}INIhS{;CZ;T5*4T~ zQ<_t!@4$YboxG#iscZ7`ULY*XNuwvhQ=XchV{$Yuh1Z_S_#3sC(gJa1GcA!FyNfy! zWagrsLuo<%)oMLrj$t?ro2X0;4+5QwLtW)Q9BH)<5smqrpM(m?MjKbNCAVatgHU_I8XZKWAJ6UP z;LPbhrersWh?a)4b;vwuv7Erd_i4(#?5?t`oG5whajGcjkSBZX=sp~)6t^wc%@c40 zsSQjqC*(CIuE=MrT@PbUb&7 zlNem!jvI5DM&gf=x_hLQCoS0s2d&3Pho)J!kN(oMZFo0XZb*A<_bg+E=F*)FD= zXxxeNrs5VdL)l4Fi9{YJ&~H8Eb-3ztK1r34#dpo8-U%l3rbNeRLkgGDQIbh1ui9H! z2PcHqvpMNNT+o0FptsXbdqM%39!AJ*H|%pC=Pj>>7Imq_k{b44<{DXR%DDwCbuo0` zX#a@gx#uI8*DCUsm}~r8tI@Z_((NsVCXr9CUuuCbRrFwglqNE(CbbY*U08=gdp@m5 zvp@EO0Upn$D`>Ep0-ONf*DP_Ox=uk#SuAnKvK9q&95p*zjEKblik z6p`|2q7;vO7wW6etz~%}lZMB+!aI#=xC+Pde6gO!nLQiM zkks4$I1vAQH)JCfzWAtcDU{xaHZ&%!k-$b|A$ac%v0`6tAW6T`#1rqg(>Gi1KETEB zcqs38svWNjzF);NLGwP9vwH&X?7WAk_I_UjW^i%?p1^yVH>*O`>t(6xkt zpRVd|EztAIr3?1on~Y;2&Kl><^(EjVJhlfQKtj(p(mBUp*nJ_#7pTYk_5<8Qr?)v^ zpk__ys1Tki);@Gxk7o$99+p!L1DCU(a``jLDrGJyE{)`Zr`d{Yv2SK4tu-yow{Z?g z@o`xbO0MSpG-Q$5f=5R304RQ;es!_F_0{HUg&SRY(N(ezkB+o}BjRY}eZV)u1VA@w z1;3xpYL@T}RyyA~QO`74zv7fd^QF9YKs&I{_9P)ch;*wi~rAp=dQ^txg#E5>cW;v$nGn4%m$bSh_#K^h!h4dqaT!BE+ zr{Rk%!=GQw9%x|3TcjvVo3a9GFNr#xXD`U zA!$EwBa8$=jJWXymoly(qqK`c_#mkZ~cb<0OEvxb@y-h&!1S&f53kLwxvJdmcIjS z04lp*CI3hK=T{Sdu_6CH{`0H;Ka2g&`TT1t=+_PY)yUt0HY$<=azbMN8vg-IyPwba zo9_ugqy2iM-|!!Rqv_wne*jp^A3gL}`qQt<{<@=o3;&U#v!QeOzX5;P{{)%-27msk zC;o51pI;6A^_2fD_`|^PA6!^}9|7WQ7ytz2e+B-iI4MrQEJJ|xiHC?GkQA`CNVc^0 zGC+J@(Czbu7K?Cn36Tg0r4S=7PWVDNB^D;u*fw40J#pss<~lLH*m38j1m+8o2}sf5HUtJ6yS8vBci}WJhBZC)Pth4j6>)F4hGSG0*z#@^K-|i?<3GX;RW*S z;*ExcLI-;LBg|$45N3l&fqHlf%zxjDB*Q1#gacPpSVFULvOfkHNQmnVOk}MsX35is z0pb#{U;x}15(1=z_RbgaILgQR09?46yR99pZNs{=0rgo4!s9y?REUR|iwkDYGVHkp zj}bczM1NLW=f=9qaX4QtQi_kID0@%$#XJ=}557McR8t{>l;DFb>Kyp};6B(DM7Td6 zA#@BT;vRJ11AivC4-3QXo$m|0zfW_u2bexr3_hReF;x{`6EWNqXqPL$ryie80>THx zt870X4Ip~V1E3Ih&=WlwTd8N5aGaH9-m(f19nqEkvI3= z5T5UZVFCHfht#E4ukF|CdZeWJ1c<$K@DP`a>&a4Hnogr1jw~tlNHJ5T_Ipk3{)bluh?5Aj)QHY_rnF_vE>Nenk|rEQ z(aY$~GKc{dRp>K<0y0JDDZPc@z zymYBOk6oa&PU*+?IS1I$l$>U0c~SqcO9O1>lV8o=5$(x}6y+S}Sd(q1(yg8+C^XBS z@CMrykm)z&2Nhp;ok`2%{v%O?8jR^Zg5a^MAm!Iv{+{;6&{@f(rxgB4UIvTZJmUbyi+5<;%{-04mcR}gKTw7461bPGI&W6M5mhdC7c1kJ~$j7Qtvvyv!mBqjDbKOxs z=EK;zdEHuN+R_(>!Wt1DSiknQFkC)~+Ll7m^;r0m{J<=B9^OBH<`HKg_0!=zqPt2@0LJaZ7 zUJQNnreu6nHePEephj-yL*b0m0BuoIYn%EmoI#Z+ev4#!+|9i%3P2$uL!M2U3Z8Y?&4ddKaCAxv7vvD*9uuMVBC%n3W z&OjNhT72qD^*Frp;U&ziuqP@AHjg{jY?n}dzmI(=cTb)zYisQj&tJc3wEM%M?={Gb z5Ha>T8a?^<$ zV@+8DMQhfQg!A52BDQ@eq~F6qAHmRu#kG$!O$JSH5fh}i+%%XNRf*djHFs1b0dHXR zL}%@~af8QeV`69Pk;^s(H%M=NfXjcHZ%xY>IFEFOEGM^_^jd43r67j!O03&%n|!_R zzqez=W1@#W6e$oi3VA*UVPJRhBJ(;=C%LN6tH787{*Wh+exN6TE8-raB97=R7%e$d zTN%G?FNWkoC8a#uBism+kFdMRU)zAaPVUv7bl*#48A+@A)!aY<}73S_+Is= zL}^)|=9`c(j^=!)lTWM|I9iG$#ucHmPHmyd9Z`8ZkTb5k%434`9K?*>@NBCw(w^EP z+h!VVvp3T}GD2>^b}qP)Yq~e`ND8V5Uu=zP_o#0ai^*Z+KPWibJx*Gp1kV(Ohb`9WcLEf)3j3_r%J3jMNZEGs_*h5N~dqUYC2|asw?5Ryn zmbR!poj0txNYbErXNo`VM~#oxo24LU`-s-=D7}uklqk~bA-!K`A_yVlx|P|@QZSG{ zd80tX!Sy1KZp+0GRbTLF=d=v@@?ob=E$5-sR@K$do9A??XibUL!DW}eP?Nk(`%T@> zG)|so7AJKC&XhWZD)emc&x0{mLyU;)_ls66UZ4Hw~ zzW&$hhDfE=BY!2&hHM>jpX4AC1!w)b@r{_{1paRQ_gs+QC}^csBAF}Z3YTHO@0)RZ z$M)9Y&~cM?_@MMEI-w=OP?8cJr()nWQ;+zIzQ4dP9xH?h)4Sj~lMmpFerIFP#O zPdI8-8kbnlP3z0k-Z;^)3W#LU`G$O(f7(2PCR0aYaq6{I(uChCwoMs75osR2kUi&9 z-!Gq#&^{Au;vL!dLjp`MpS+%v(gly6UjAgCM;Un*Ia-&)!?9cS(r$GNxRqSXD+ndgZuME3*aA$xFn+xve+MgLVQ-|>(r3Du< ziHW-cijOiiSFCn|tKuI7vPGpg6H6>v=wM zwiQ~=YJw*XI=M2L=yHSxNiO$>k;M_Z2tH-*gj6-py2r;?p|rdk5GK;r5CY_OZSv!o zQE8dR=vs>xUl4p0JZ;yU%~@TC_P*ZiB=#J%9Jv0kNh?t z1G01R)5>J4?5AQL61D?{u7>s)w@D{z4GNyDl3guW!+?^J9af}d&UXmlM|#qcjlrIq zz$zmb4bv`9hCu9vVXD{sv+xfK+6>g=D(`H~C*XF-rLN-6k*~gnZ&^p{)}PPT6`WMbXW2|PMizNMh(P0U5Kb9gy_lBMC4yyRX5cAmWP2x_ z9-U#Y;i*4fECisGaAZA}fyArPe@BTWVP;L^PAMuSp^s7U;7Yj=@zIgR#LX1B^p3r= zbEffVZcpgkN?WMi`O)g;wjA6qPf4ky$XQDvD~`q1FaythSfP;E66m(p`f_vak*{?n zIq0g`M7KZ|!!JZ{@UG#wU6>2~TaE-t5N~=mp+}KrfMrkP2k^|y>}7AHK;Y^Ef)vUONzW#GIDnk&+o3-d}*Gi2TfKc z=-u%mRwLKSpZEOKmuW)yMcrFO@01=jsTj)aaReEZo5?FO#X8>G>F^!0 zdADghA?31D;sLWI7k`jhmxR_MAZcI58yL#+^ZoNN^J7*+ijbPR20wRK3}Wx)d)WZC zK^yAAS&}0?Iti^po>*zV0^dxBba?M{SNQD5&$uh9RbhE9{rn3j^;X(32}moEZUA`0 zJxSV04=Xz!E7van5jf5C*={c~vJ|YA(AuEwK}KZp-dfFU)4h1uQ`ORw?wq8@>&2W`oJKF8@p1lmi3fw&OscrLySWO&A25N8N+xd9Yvp%F|&joYs$nX z`R2K$&)UsZfm>Hl~8`sn#0v@l-3vgF4wbvj;)5hx$tZ=xueg71(gn2Iq`iM6ZJTe?2sb2w{;9oDOxPQuyj34 zs!?REHr_|F&E8YOvYN%w8JzB2zTUp~&S$&)^8LaipQ=w16AFHd<)A*1>?ztxy*8V! zp`Z;a1UiTwmh$~Co`4J+(RYYDpMD6`wl!bv9QQ02OG!o?t$N~t5-((0_>qBmCZ852 z(uNX!i6N^B+@(lrZGZGOs;C0jGB$1)UY!}sUV&u5rrGXEzl%CEJt0+;ml`0|d%Qb2 zuPY` zF}(V|;yr4G4JLrgGh{S%J7&{?QTHNkFb@y2n-Wk-y4;{}sOrmnAxdiG3na2-*tFmlvSQm@>jWq1sUH5}h} zfcssdFC&=gcU0o#C)cR_c%KZ!U?Q()i=k6SY>7QDym{#trAxAzhjLT?wBh#V%T$d#Ks6B={ofmQBjikJWqK6K#1{OUtfFD7=TY<9m}eBzx8 z965wB_`wJHit1aQ9eQW=3qSCquW*fcJVA9W63B=~4go|sK((`Ln+<0n}~B@lDpb>C69UlJwK?o0V-(enGi1EDRnKAhx1>$7iPNx9%^9is-g_J9BIY?zw2 z<#bh)R7%45#c{V~)$?8@q~!Kf?#~Px=h^ZnR^a7!f_ERXXJ_1MPPyboMPSk;e`eS; z238DE)n!9@r)m-uX_3PbmA)s=zr})=j9aNIsW@*_<)E;P!Z4u`iN$P~fR!78ua?q> z33<=mOZ>1-85wpN7C7o)+J@?+(6MaE5F>o54!_zIYN@w5Kiu)Yy|l`6z%)!A<4fG- zx#t)2R2R?eNKtmv7mp4!^9`f4=hm{%iwdznz$tZFI*bn5%0L1}hWD>ITo&1@HQQHn zr((}Y*FTK%d~Jboml%;a4l|j@yyx7UncIjKbrWu^XmkMPnciRfgq?!YnPQh$x3ou0 zN+wImEV=%b^|QN}ML|!KQ^c*AFS>8WA-V7lx2$p8p#GT`79%BN_PT*b$;#g9P7F(N zScNLnjC~f67f4K&f|kvzIX|6*`O64KYI#<<@ zwc5wsq1{`~vJt_NNc5RqlgXw&X8J;7b>bCYhY;2w>zS`Io@G-ngGD9pB*QY}P!LfgWYrrJw(JO zA+yY!h6bz$R~N(6Ynr*dKUM=pq~+0b$K|52qaA&@OHDJGCxbmX0~$eMO!8o2D?T9& zikQTlm7ycUb#CC=4_q+JzsPq_?GV(N5ag!0#y@qjG`TZWt^r-(ST}~O{GtX9Ujf^s zN!GzV?v7r<^M;%2jLF#5M0@oq94z^bSi72{7rZj3RwGlt*4hfLtkUVrfUKP4NM&4R z8hy{w<=7&y-8eup$C_T%l7g^nQBfB1?l=S7TQ)|t0DbZ3qd2Vm2{(#lNEA2^v%BoL zSV_P(F+%;>=2NT<%GF*1PvF2-aWj^1?fMkNCv|hH7>K@#cHXlJ#YWAESzL*<@Y(y# zgT+ch>lo{Ok2iLoSLMmbwd=Do~2_A zs-GlN4%F6RhUboz&Yz;$tkG|#SfQmW{d+*Q#i8K2zqYzzv#)<2rmepWGtBBXn3KxZ zE4>HoC z%bw7_@xqd^I?_$XxCxsm3j=vy*)%ii5cO2-Zt0JMz;q-FlrcJAbeIZKBc-0V({p>A z-}zN4+h;%h@P`K{!y&DJBz9tVMwx4(5Vp)sn|-wqi$LDZEtLNy`~E(ufVo6LtH9@- z@nxlv#e7nxnZyP*y6eIlfvwG;iGXrs*34L^LP#wiudo+g2y3Gh>xy0&NwZ zEEPo)F4~>vg9RH3JL*n4LJ`=Xh0-2MH(?DKSWu29PaaOs;4C!cqHRyHx>YJ^PQ$5D zop8h`9Yq2@Ly~lShQ4mBXri2|enw;?&Yp!ElM>ZHy6X29PZQa&M=@9s39@aaqh>h(=HGD@1E=w3Ha|*xt zgLx5_a~)_>XDb`yn$*k@^UNKthUCV<`ew1Y6o#@j6;xKFN!$F_iHP;8yOKBjLYc?( zrF0bd*J*2p^ui8FQgm`g1%(i~?T1QAc4DSfB>@~}S&~yvdbQ~2J?AD@74IHm&Ab%( z4gyp&?6G3&?*~pCB5S}j+sO}eqjfUyrk}oa>geFA-hUmdIq7}T#1%n9iUZF0CUY|( zqe2{v&}-`MGhn`o**{pGW388p!4TMHZt&8!is&XvaX+f9_gcPdWhO9nd1ka#*O(If zRnp;r95BkCWKk|u19L-b1J8Jabmb+D6D}S(%W^)|^4+B5p}Bx+wQWiKfxZ zqK?d>0Bv5)q*^4%WYPbH;DmSEy_ijKh>|Z^O338!)DJRBQE~QYUl3eO0SSL|Iou68 zn1RkoW1`;#LTKM1wdKil+#PlH>9||XV`2Uqzz;Foi$t zK>tHJ%D;|A`5Aoj%Ng|FrK9}oXcR!vKWst&8&wdX@E^9IKV3rf^n`58%m97TKVS>? zpLr60?X2<>{#45-5Yj2hixblQ%%LEp<2P_Lv$6i;6osjyqpbrc9i4%xo|(0Sk%5!F znWG!6fsGaAugnes8+$_|dqNFBl|TP#{Uk0790A9S0DTb~6JW2Mft8k(i5ajD%f!e| z%f`aQ4!A_i{Ob(hfB$0-T*}Pa;vY9)ZEfTD+txh$?|2CR&x;O#7Jfa4{Ldx@0lV!+ z)_{`?f8}gQ8yTAE3D~&)d1EXbEVPVl0QVF-z%#|p&cXh(l$@SDpb+5x{^Sk*Mn!(v zb^vt}{<&@cFG&PCaVtF&BLTucehU8iQ~YPmstN4Cz@}m<;Ll4d{)G_1TT-IRMHg=3o2#Khc3d3a}a((=+PnGqW%-vCtdS>$B*w zv2f_qGa9k5u`=iz1LRcy;3@tnU~6b>@1n!Z4p347MneAYn*O)|kVgITM*Y(TKmgCL z44!|w0HAVyfjRO&hvKJlNZ!U#&(R1_Ibf2}Dcb8DlX>Iodl}(HQI5TRG4<+8Y_sF|jkVu^KQK8?!SR zGjJHN0x~~X=vmoWSvZX74fU8A=-F8Se>n92hl$DnK#>@k0hS;ZHh}Vrjh+F}u`IOA z?DT*$91MW`A;6h`d!qiHh4VKoB4`6xHGow@Cv0tCV`ye=LP)1#X3cNyVD|S389gh& z(h~t>nEYc&{8K8-?_K-5N9ymE#EdMRjU3Gk^k@WZEDdG#EzRtl0M-88%zxD`!20v= zwfh?-L-&6(q>eU#P$3$9dox25qyJ&7m;jzOfW3$f@Gb%Ti^&0$7%o#H~Ed$g4%A&^pADA=1vi`La|IxMoR_;`_%rA=?8vZg7nG7~L zK%*AFK&A8{w7AP}27e3l-YahEYqK#si_;Ru(*s9CAc`2f^5uX`w*;XajRb3#X;YA1 zNe59AuHJ1aiTN4avp`_$Vz4k`_NdvNAbut>UNbY!p{~CK>INoXfgERnCrHrLL5fB& zdStw|QK*1bdn0{c7aA%xr*@9UbsB#@s<^m+uvG2Pbz-+su)Me~HFGpp()+u93>DNA zo{EtX+Uu%Yy_0M?)VS25-kL}}5;*eFbRPti9OEHuG4 z_}bn#w>OxH8-=`L?^b62AKAe_CMq3>nU~Yjl3y7v*6)_7)Kv2mdvQztLwLe_~31(`OvNdAR;~F*C3da{MI=`pU z>(qWV@mJITK7ID9{y&TTww{08-oH5g`zPPu%bZ_XMMX{UU(#pH0I=*o(Ptcgl8?X9 zXaBZN4Y0EQBYpO(vcDet-=fcye??DO0~j)I*0!o5{=_e!0>Mp?Voz(|As{S z)zDv0{NIvj987>vuRmg@enwCMCg-0=01}OZh4HT=zzq92@(zGcF+D37*QH2BC)I@2 zM(}(g&H_=A0^@^(Q){yjJzqaPh+s@zumk`}C6a&&{4;@y6dX9#TBwEp?+7ZlESI(V zyQQZLr|~7aK|C*FZ*Y%%JVmsy^BqWNpaLKXP7#rC1U^0>M2ZhUU@%B_02GaD^Lr;^KB4>$^bUDikmfz{EhDy0YX& zM>pY!^tif#fn8s+I^N8?X|{I`56MU=LPhexyDIQO#1gi>@Ogl+_YT461Ks&Pt;umH>>WD|-+W2-^46{LbX2QiQ(mOrIXi-_Z$_*i#Qm)E5*E9{ix#9M<*G^%A%X z>Pbh4eg)J`3*i=`cSE<;qUQ`CRx5#l06MQ9;6-!ilLM&H5P%UdpOYoJb*w57)WP*C z109`$-g9w>dY{N*1^WwB$O6P_mDJAsgp*L;))#<-t6c6$``1RLzk>lg-1(bRybZU& zBfa1`1iFI=`SE=LlY;|tg5>iel=1IGXSj6=c$M+@4ytszH&}_W1`4sDl=Ddey?%^_ z+6KN22I2aoE$8!%7v)Vvh!C+0AL0URfgeu~Dz~#$CnN{nXTt*Hi!Kk4V)o@YqyE;7%-&f+nf!n@Wxl(_jK(v1W zdg-)!Yu$OXf2yQ?L+E|G%TjFLIA4c5eUp2|06m5YczPXfzO72$zu`I{tSkh%SXX2^ zATTHk4P1V6k?BS8>+bBtmn%X*6=3Fw3KR=r zR|#RxQlzBMeMp8hJa3qm`Ken&T|{(uF8RDLPL640B5jsfBIE3_&hV{teM`vui z81AhS>ufP02l-A#w0StI87uQ*_tXOEQ4+I9ukPt&%*O|43eya%h4DuflIv)6I8F_zOm zo+;X4JNYo2Y}TEt_+rxmdVEJVPhx@4rO}!%B)NtQ(j}|nxXC; zHp?ZgIHGatr%JL>`HuzwwzaxE^!sf`zdBwTe3z!q;{cX@W9vO3>P&ablnF);-U`1! z?9qbDbn~kHliX`0284HZ?(^u*{da2Vi8bQg*1YE$fmS|@hLXgnfnr6ThP7<+yL z1hk=XofXH@H60gdqGRB+I|)-$9~g9q(E9FGw>3Une|hGR1Lw6JS?;mT@SxseX#qrO z7;?K%Y;5rHtlMkX%?5qrvA@_x(H8D9<>)~jYeGRLTF(6VT|&ZcfdcTwI`C{8tUC(5 z1aw-|Z5;a|_jvBg+jhcfQ(3zS4W_Kyg{d#PL^D8Y9m>NrmaXCo`Q0d0Gu~Ee zSZKU`A8GgXV8v6}Alf38l(irc+MP<<%q7WuMVg`$%weAT1r2Gd8OfHp_U_1Hrs*;( zRVbUeJchs#BqTddw#Hy~;zouFpXIwOKp++39rwHokJEB2E-@Sl(l}{&Q8?8n0@Lew z52Ed)qzh%kgS>BL(t;Y8_EBV}^rgtIrDeLsEs^ZvRFO;MpimL|#8F4mk?h<=uFte4 zF0Gy)6E{fO--~4u6KXlNjd|h&Y-_Pyy+m10+l$CY0bTAr9=kTd>1Si8a$p$&sVyh< zA|2q7>s8p}cd9XXB~m(P+hN*B0jXY(DZR_041+Iycu#|t;cHn_DgfKsn9LuxwGmmv z$HVNa>=&W8mCZ=MGg3ttlM#2ArP#UXlu=5>78ent?Y9qFl`~$aR^kaZUz6hmX+9fD zbwIP*iziduFSQ5WiGWU|RO(QOy3Te^e9C>``D7OXu&qI|YAUoP5Z<-Piweu@GR<08 z<6UzucLce>92wJ_`^(UmbYxEOwRn`5kK19+n>Nmye@=Qm!nZ2_OtGW&+%WBaRm(;^ z2z~v**O4ScB4h;p;o)7bORz3g!CfoKXk=ak+mv^ip#0u}x5NiBh$oi|U1z~YNyXuj zg(0u}P---?cF)sXE%22fdxtzVt*FB|GFRc}2gDYMGd86TdJ1$?s>@;fSZR~(g`$;~ z-sqJdR|nr-?paQVSWPSdUkVjT3RMyQ92(LQ6|iG6BerO~Ykd>i#0OgAsif_m{Bl#tSM9bM}0o|iJcA7@` zEN4Asd(Yi`0`O53j^ z4)eJwU0Qo7?5A!`8L2Ju?s?d(-SZ_Jpj)f(hQIw0v!(7mBE|DwEL@qz=+e-U0; z#50soAb`!U2<+J?L!h4bi4^v0#x_|h%SEh?B!E@pU)&v@4PDSe(NWKi9b;10^|MKk zwsLz))8)t>o4k!el3|R;ltu9K>e|l%7_mj4Wrd^MIF+U)e;NlWlyR z)7lzzVUTju(2kAK|IiaT~E2jW2xWiKq}T}~_I$DY3A z#=F~Qg<2=ij;=+xv7twV@}VF@Hl@H>r>>nBy@_I6g_V16>l?i=yvX6qnahtBB};~> z{nyT55!dL6WFtAz^D0zF-XGL}y@(?;Fdx3OgM~eylN9UC9UE>Vr0lKwFN}r@{{Q1m=%g3F%>uiU`#;;8ZVi?<1hsY`0 z(B;7coTZcfkz2G%A|J%3_hUm`TVQMGuGD)(J*%lgW0I{p9l8sTk?# zCPW${sqw~5l;GtlC*AWJ#5?+~NwxtmQ7)4PEo#LAR0;8=IiX9 zDLfJGgPkj4tFgW~RL9M#DPWx>G~VelrD4^+bq}&}bQZ68KjaWETOK#xUX4na-+Sk9 zI@Rb@d{(>Nj8NKRZR1|q49_~R+6vgLtqf=Z=+-!>YvqwR?TC%;BK3+6#nC4Vtk{BM z5Wpe#r3;PQLo9D+$+B_{mSKV%Uca@vGboFmaOWS@N2e*7Z~DjLK=_GQL_!Ez&lF40 zo$GoaR$02>xT$Wmu$Thpjur3?5<#_vzA?aOf5`}8-}?W^d+VsUmPOke2ofx~1$TFM z3-0d0-GaLlBxrDVcXyWn!QI{6-M&t;_dfgBednHW$9LZu?+==0HPy9N)mpWxx_@)d zak-w+@Ds7oThIF3hT_}EEXmeW&vF%-#Tm^7Axz5vSxFeB?tJhw4yDEcO92{(_c&iT z17l;;-rKrBXa@Ibg!3LD2Jt02^khYRuOA4!g*wqGHSvz($m`(EOp3pYPk_V5y0`O- z(1yb-Aj8ad77h%;IH|DpUpa&?fp1Hysn}N;x*2(G!znY+L0*i4B+_!Pz|Fiag|YSp z#I4nyZ>#K=oz%T7w|!1Ewn~8?{+bgdFR5-NBVy7rb|x!qol=&?C$m$$xiSU!@aPJN zTQi9Aaal3OD4z>C&T&Uk?iEL*I59M~#y}M%CW%wnm&{%wHe9n1qjsIJ%%@PQtZun& z(q=sRS~pCCwJEFHu8S!I4lfYKJjZaeW1~tRNTG||X2Iio#9uXCRk=F3?NH}sb??9N zD1_o+6^%@CyT&Tjr_8L;8~Bh@XAcX@&^AGGJX;=xhO-jfibx<^u#ln=Vi)pUay}8k zw7%4-J2*oXLz=h{bDxbqOjt{z{`e6>U~t?m9d>&XB&5auM2^bzXvzIhhD=~#X370t z@7A#DwY8>in*{@$t9N^sBby+LF|w-v3b^I7L&^qseJJyClRgs)!TiX9T$;(@h|a}> zOF!H@K-^j$rE(_uYjY1Rzr?}#5V!C~73;q9$<7uC|F@uW;(P@01wF*k=ww@MB#aGx z)l~!YGouF!BbKBb-jff7hb`gyNU{TlyGvPt%Oq)?xjcjr(>qo=`&u?@^==m2{b4e& z7JV%kohaOc8BG;I1Cnk(P&Euh<0r!dzuFMc5IjfAA;06PUBc$kkc*iwl16^n@1v&- zHHy2O@>f5rzom-fZM~E}g&bE1Ak(c^2N8XE=)RxTL=3P-998<9XiXvvf$3t*Q;D1? z8HMXMVQ8HEQ4p_E155rk$wUjlsuA8KQj5V>pUdMb^|K~F-;aG4ku-K~QXR@7pDu-X zr^?Jile48Mab;RC|J|bAdLmMz^?g2#GJ|`az`BSW%0zgUatJMsgBOQJQwbVJiYjU| zrPdSP{KoSePD5+$;&3wT`~bs=8QBx`lB}r2vu=mlpk1D|tlLE5J?Ha#yJPjuN>314 z>Cm~9uVJ1KAM00ZqBD>)+#OKIR~bLXU_@v2Cowm*-s`{EDixV-u+i^c$N0B$OnF;^ zw)%oX#d7#2)x)($ILo^qIiR4}%&)#_uUAryjjSX>x@kb37ynF(wOmf8(Zt4XIg2`k zH~AdL79Ci#Dy}-a9+6__x0c?-+l4)>;JiH6cz_~m5z5?p;{Z1{N&u73OSXwt5QB2O z)h78GhmhkQlR)_)QCrre^!|PXZ6~3r}vvWK>D8*iV5+R zj;$JOz0W2+YQMCHf%H+L7`xMO-I-qAdpU27uZ+VR-)=&xp=J7Z)zvlAjf&9|`N}ly zm}K~Fe;87MU_4)#JVC2PWHlsH;=~z3CQs()8YV1BEZhxvz!#3w5wv95yQ`Sf)`jX( zao|k0T89)%)mCzwtIg^8daRWTXVOYOPju!M)|KPb3Rwvw4j3m@EHaJl_sKS6k1L$zJRzjLfKUN{P-Gk z+sst4{q;NG0c9)x;>H2kmN^Lh=1TpY<-Ng&ys*cK^A9|YJI%IEak{9GVzoH<-%fRw zJcO%8BXE;8$*rr7MUgH`Ya%~>|5zBJi5SfO70PenE(G5;q+`$I4&poHeB6q+G@?VyaljNrz@?>NNet)+~ zw6ay!$N52M0})r_@`28NqqA?XkW@zo6VeZnhLO1Hx);m29$W$~RjI|6QO!tibqHzE zcGcIwst5>SP+T!iL#DR&K1hGrcah>E5XZXdb$d>hLyS6%*XYjsQrZ@e+f1vT zFXxG_qbfXaHj|fx!F~OC_&KH_oG%>oAlas1ey#*&RH@kCgXf?(mNE{6_|%51-w}`FWg6=Q4*=| z@QkiWHWto(7_@-AwZlyR$Q58f-r7Kx*DT#Ug8G4_)najR>Ek5!vV;0|X6T0+HiCs~ zA)6`wcpQZqZij7qW#l-@AVDAL-ZdZoVyc!CNrD)!-EX(FBtb7zNHWJ?ODVii}R5vj~6@mUB9tvO)Nom9PQ|;F^bBKtMi^k;V?*^c&#=g ztIO~;Sj4OD1b3DerMuJbUy!P78QjgvU)G!lU4B5)yT(UD9aMo=moe4J|=ZJY{N zu#z;{Psb&Ld2CIHT0+#=#?ml6bhVFK#AW<1gMSnRLx&phZ3=x;cZW%+q)`+@B$-W0n zh<=0DA%>NeF@Hdi6XkX5!e(p90a6-;n?oTYts!+$bpg@9QYT^J?(JaNQ%3r&#}~Js zd27lc7E{Vs%fov%hl86D*>+=R7*LahKl9d5!OPFp94)IdPEb2M-1GEg;y7ws@8Id* zXRAbCRtA>BGNX4a|gCgqkiXVA_Ot+;PrLk?p2c=n#7vl#inL0d?m`Mn#32WV}M`}euRbHP$7 zo)m0&W6w|3rd99CzC3BR9N6wwB&9OW?t)zu^mFN3a(YS^HR{{1kKxaT2%q+1hzaRP z&SlmimciYl(pwvi>x&|Xzh5HrO;yGeHO;J*K0vGG__Xpd&#E#mcOH!wC%1BQZDmDj z$9#YiX?=B`T%W0~ZL;F#gnL;;Y<~M}drI&M>B`PPeXmr~Awe9(v`xVCPC{ts_3U{(^J=z@H=@khgY91o{Xtq~f?vFSRm^S5LxAG_+4j7ykkw{lxVpDDoDR=G@h&a>pA9?=v;hoF`Yzxf+_cZZw(tdH7 z^|P21^^tAXM|#uPb)YSjcvqVp8&;)22c2Wxg+nBQkzULxm?6)7lCbq#zgvr36-3fe zuQRDyhgK|XJCsa9SQ$ex*@iEqu$e{V$+U{F(b82K? z{qW3CG1c^EY8r!kh%qH?rB#SyS^5F9L z*4n+2{0_MxPHT5O3*1lFsLiYaWPj+|+3m`0<#F+OVQ0!O#Z9b)( zVZ4bGY~2mA947Gz%ykAEkhf+!oKb^ZLrkwdRevXDKhqScqH8W_``x)Slna$2|8T}p z!|iqvGt<`;dGTaLxFKJ#fZ`_QR(;7R6heijkf#@I1tYoXsXsFi-^FIxOCgrIV zV1wO;?#;Th>Yc7V=#P7~hXh_`(8^0+ZMi|`;dkt3R2IbQJE@WJ z{>U|cdePdH?}KZXmW}9Mtd=g5R#dymbzJyJ2SMiD^fASijm4l*|6V!1On{UvJ>98V zdGdKqn%mz+O}==2SMUMnQJ5pC56cs8QUX_G+1TVMi{v$K9&0!;ex^}wZ+_vzd$d;- zIZP8hBBE-}uLd1a@{Qv93d&&0Y@fjGcjH_|fg>s9%4CROOFJKJMvieox$ zbYA0>)ePIy;GA{J*l5i+bS`S=>R^WTAEM9jCUA5)e6{+X(!M^6ZPFb2_y*@;T?z|t zk?Wg82dFG{p(@5qm|cX$XG0gMq>6r~>}iV%dkJthx{O`pvEJ26;<_2;ZUi2C;;GLA zc5(@YwI#aWo|g`W2MJl7ujD)A59U;d3fRvK4;sOq+=y4&|^iz z)+*|9X+LX#-pGtj)3~se;~kE~$k(gwnJtq?aoqIJqFmq9hhiW;9xg85m576#s*Rl6 ztcHA@<6X6MRfv>sh9@Vx?i?F$k7YWpHp>}XD^MTRq(_*G42?V!N>qOzdo<|=yN43v zvD0{IdON`9{FsO9#O}MnnCU{Yk)76(6|p}YDZ#P>X+)3*m%N79L7k_73I1K>%Gfx? zsT5{A;M*#5#EFjG;TuV9Hl1q(I&_NwC(I;`ahrqUZ4akc$>d2~krK^D!AO@7^0#4~ zNb2u`s^z(Ms}J`^f#yr_+Hg6`UqA~d=#B!Z?~#>_XpY3aFd>#HpX6e4*`$^1rZGMx ztSN_M9h17R&_b~!RL4n_xxX8EC6fZ_)_eODb3NJB%Q@5Q+NGOxnEGTk*EvJM=QxY$ zwM9+eYug6YXBM-aB!n!4uhRW^0e>FvA2C8PWTMUXogV?VzLV4{6Q^yWq&AX=wOpxPD|G zRS$!vPxjnk9IJj+Yk_0U9B{l>=K5S&Ez?bWmcpdjyMMS_<%qrbm2 zM3d|BNJxG)J9($k^O104aMONC)W??>|vWf>6-Of5IxRhkqf!EC3J)55RuWu;4M%vjfP-Uyr|7&cAx3W1wOCEm#jwoCUxJvH~n)=xOMG13&-u z%soKyU;q3ICjImCzj{L!fSJ`lqDp_12e3_ycq{;Hk^zsA3E*}GIB)=v_Q!L&-;eKa zs8T>tCN?&}+x`Z8{@nk643_>qRSGD~_OrcyEBvFme_C_>dSv{$H%tsa8s-mvl>N^L z*x&P?f54bzzc`N{Ferea{RKj)s;T4s2=$|9XJN-zK&?L!FM4Kr z8b$^>Ry+nqrvDz|MGt^q|H63v|4yqhu`$rl(=)K*0l-3fRyu%*88b8Bj01p)cmS{v zfEogx{HtUkJ%Dxl-#nDx`;3K&hW+P`(=##v4uS1Y2luZZ$`3f}ccA89?v?*d0QuMX z`^PT-K_LGEtJr>lxIeE8dBC~;HaMU{D1dq|1-w@ zr+)o)x&QTr{F5NXqy2xdUw@ya{ZKjlhm7KXp1J&~Uw<;LKW@c;8lZpm_5XwY3W!Sm z%iZ}e$njs41^*3I!GDPv|A~I4r~kKRH~*`H1pQylVt$_z{5q1q9sGYomH%T0iNDnn z{of=qqNo3{w)kUi_Mf0G6y(Mx9+O!&Eb@gJ3eKaXHqUwM=s7~8q{DDkyCzt zex6s`XgbG_?B5Y{b*=jF3EWqZpVD}rnjca;%MJ#-wlL#31osRd0(}!opjWz^7PJA! z@+`Yn-dr`U{<27eqb5gb`;a_)fFJI2f5(X5m&gH8H2(G2_jyIWP_=|{kOXpSM$nd` zDg5-=%SaFXX_P^#1Qfxt$tI}5ZCvc-k(B2ai90l%dOKD3N5Nc^+l8fD{7#o8I~ak= z5<3~)?=kZ+yEjskHK9KD}j$eq@n16{^5$fm{es3a82mbV!OnW!i0-JxR z9Gk4+)#9Zcs9*nVu|jy-7Ca@EMOub?L3%Cc4m-&j>S#In3dGKtQ1Ex37yWAB-$#Hy zsetGKA?rU|{x32hdir0BwVxW9UpgR0Iy`!YpKG?C6I@wcdwWA$D?EDo->$s>qY~&> zHNV&YuS+vYgSVgl@$K)57j_`b>`oyf#?~2txIYD zBnJYB*8Y&F{jNH+Rw$VO6HSGqazZy(8eN0M)qwBCKnuIRkQ?Xh1P#)g@Ewn#3B=j z09qkX5aJhrR)`4rJG^zm8@^=R3?G122y#9)^2C!YA)4{QDEE~Mq~WL$7!MMXe&vT& zh?NY=2#z192H(WEU~%}%4Ln;1yeXVt*7Hj-SbPoR`(qk%ytU2EZaACEEs?AggGLu% z9=aOF4xj@_As0bxpmu{d_IOrf?Y-wnRc}I5bkn@@V7`W%;J$@|YJlv}y^;0bmHLX- z1^fkMsRWKraTa8+Nf5y+s`iEG4CI9a2Pg>l%Bi`%$cxz*-xE#n==iJE3A{fp%sS{N z|ISQ+3<;HY0c{xC8{k`wfd5HmONzS(6U0yO$m(r7D;%&l$`x=VW|$ZGRgFmJ(sajEJ*vp5>^dV7eD0w4!BSTH>B53td^QJ9F%Gh0WTviTnAR@znyRe$;Q@rsT@Mc^J>xAr z5Fo4`sHexr+xzt-2g2vMHri{th!E>5P%tm6yPf>&e9f)4^xA{DYyoC% zYQC~6_}pn1kzb9{D}U(1eqqfI4_K1?4AKSpax2%x)k&va;D-ZadHR%Z`Vxxy`ktK) z26$S~wac^B2*@1|@65}V)z0YD^^)Se#b)Oe`N}Bt#Z3&wpL=2*AQt)(1cFC~+!JxO zlVu(G79bYFkc22W{UH{*LlC`^f?N~U1nNKtCU|lv))N2%zShoM6%~NN1G>lb@%9A* z?(^bD@CG{4mc_yYdPR!?dfV{)#@d3+`wk5h7&P+Lw+je(sy%~@59k^5C7#WV7Wu$v z&P(G=_}=U6locBJ0J@emCGEq4#OJ1|z`B$fim}JDQjeH#i&DJINL!Dp7Iv7+*$hU_ zDJBWg+}UctX(!-5mYNIp$lTI)Xgs(&%GwO9caXT3^nR=<3CA0xhVtPFq+ht}ydrxOOv6QpES4WO*P$O#N&3YU*ZrMrX2&BI)j5 zdV!w5m5rK6ZJqHLub|a8LSuEJ>OuHTDnYq3w@z<-HpI>cr^y0nFSvu;f_> zS|qwy^qQUw8mWaYk&>u!i1KpfdrM%YLT==$?OtKZjaxFc(q1uTl;K%bx5=v`2yPzq z61zjDQjlb6|0h%ey%Vn4ZkTWfDxoe3|GM&;EiP+jQJ+9${$$~sml_`Fq{v&l&+@=i z&YB3o3`UqVmYShr$_r9+bgRsD{F)v%N_mvqD%bE=&l{oiaxxC|>h+E+y>+sAk}6<% zqmaz2i4*KQOIV+EUT>)Mz@vJarp?4>QOk+jR=U^2b3 zcyNo6h`fLmJK>Vf>*x};7Q#y|$wx+*R)#Pkxwv9zD>PB}KM^|wsk`6~Wxqu)7f84p zdwm-gvp@Ja$1mVHMS#^(=sVgR;FiKB4T~0f4b2U*syk@yOxZ!VqP_6C5YtRqMzB?g zcR*rf|1WCy+Un8+Vk1F)jTBbgl3RPAL zd)=eRol}!Ef5=w$(914bkvqq3L<$b4Gnjk|x7+8j05N(V`OaEhxnt+(`<`63ovq$} zYIX%5GK|>bgDa%w{BbEk9F&XJ0qXA5S98rIl`V<8Lpdo zkjhmVaO7H(z@rX}BfsKPUmV4(4J0qME*C8``A&u&z%$=;z?>Q6-!m|532=W+)}pfB zou{Z)lbt*cR4Nx<#Gyfsg80aX{(!~Qw1CCqZARUN1B47#K$5-uyEvR2>UGZ7{mV-T87g<)$~IaFaB3Zq2__?& zeA&%$q*TAxYQ1XV{GP?AiVj6q=6LzHV(GRJpN^97Ke8>>f4LRM9u0DmrBm56@9>Ux zPNvc|ZASLzLSuQTvS*en%MsQP^7Wz|HaA65jNcp^Vf1s@B{%ybjMr2cqM}`7on1X{ ziah)2)NdM;`S^&(!LKIhIoV%pceu{D{F^kQX(AAYDBEY2&(s%_ls$o9zKK(CtHcRb z*iLOx7sfbWnQk#!0(<#(&q#o~jMh9qQ)s87M+-qU%jqx_#nCh?;3Yz;*_o_q0UQuhz6>})h zDab6!Z6KMI*cR_V289A+7>A^qbx_oimA_a(p%$UwQ$HGRR0Wy@{?I&X1# zsF5@29<)`+7&zr#CUvjs@w4=NK;avw{NY($yJt9k{I8)6_|mWoa5dUqsR0xh54Egf zn!795APNm!ut+OnUZ{w-FsVbe8Q-IpZq*n#$S(NbsR_Zw#YC#Z8*u74xYk1nA|h+cwnUdnR$BwhfnKa|yBQq&QM~GP(VM1R zGvJ%mX$FeEiBX7^j|f_1yve@7UgXFJM|}`lWKwjne5-iAFw`cs^wo!G<*>sfts5^< z3>Os2dXr5@xT285Jg0&=|AcnFU2^{h7B^?!{TRRPQVUsOI#HXd06nlmU)^!5MFgeF z5f5vDb;o&S=zh3m?&0uwcI^hk@_1mSK%U!qI|Z4!7xdjWqRgo}rG3r5?YnkPmsKSe zuNE~meXlRozId*Q-vv41p{LOu=}K>nLK^Y#FD2Z<=c)Au&2{}V%eEH$sepA~Kh>5m zn#g^yqw>9fijWBSBHw=IBfFOisf7{N*Y7ulEAT<(_7<*T!nVePw_6QXQVb%Zq#x9< zPSJm=2H(JMofuiV@T7&e9-`ty1nHJE=A9-}2g`h3C2mtXlowffFiR%fk`garIeWt9xV8*?h@>mOD|2^$ zK%KogJlETI!&up$lR~o!QCwKcZ9__sUGNaS6eqi_kVzV(TBXi{@Z6iR!0F5E&!6mZ z8$pfRhXD&Yf@4?XC{B`}VDzNfUP@gTCsqsY%M&w@Y_Mcz7*{w|SeP)g+XEqlp68V9 zP@8k?W;UqvDtzOU`whhurgb%>E|mC*i@XYvYChee(a;8`$ufc2@=LP%oX0NM)|WmlzK7N3=VrG%{MKRSj}nKF1RmKWC~B0IZ}4v-`f}*H;y;M zm2XA-<8i{%M&BKtA*`$y3`h%_^hGFrf8Qx zro=ec6wG{10YjTtvO?IGNv_{dm#0-_3{+T{LSYSemr^eQV3! ziJzqTJo%exu50VAI8>ui!SWVjPOHeV2-rkKz>inbRR?k(p%$PvrPWnnC=Q*|+_hn3 z=2)Z8rhyTu(Q^h(rIhz1XE`|n)pMoC9vmNHEn(hWY$kFBwtiE!bpa#cE^p_g;{Rk% zbSc;pPT=J=6A}$)dV2NF{xjcK@*doHmo&pR9m?A1D}_4>^)%III#T1s3|R*9!Y=}D z@3!&rVF51Uv#|$U3WZ@BTP|}dBK0y8jd$Cifgq14hinXIEK{PX%ywQFff#lms-EMd5tkK)N?9cP1A@Lbxc%oq}0P-jI0&8`*(|)>VXzD2bR&|%m zaa@~OfJCHLLjv>s#3p?z{V|&8BO8a+Vp4!jzHAa*8(OYw$%6RtS?77(_FcCO_{Bbd z@5cqW0hMX5B^T^Z8VNborN>1Ur!L|h-B+YaEQt?Gxk_jYn_UFZH?eXUxaVya8!;!% z##VbwX?L=C@<$~&yCL>Z=+g5oZ;eNG`K@r^ z4g3?Rc|#N@5K(BwOoxjJnL7ID+P}-~4qPmMH=`eMp74{OEDJH-RvFOWoXh*XOsd|I z%SHNzdCQ}FqSdJH^kRu+P(l>?psVG5Hwyb`PD4%5usEDDFSu7#1nUFW<;a3kiWpEc z{)`1{sewg#4x3l@ob$PP)D!F}-BN6y92c+d^E+{`cs|77*|yQ>>%^N8&ROzPBG zZ&T~pRo@l641Fpq_Uk2!QeU;%3%Z{87V95s7t4ZOs~Ww@Lu=@D>-UgY9LrQ*s?%d* zUmFu0G%Y^A*_L@ZT};xMUC2BRA)jkRj%q!86w~@#(v)4BnmjAY(bb?=`DGA|Ae=>s zP{JL**Sh+<2VzegZO!{o5^%^CHo7c{02Wt_M~@e~5s)bFqVkZS&X<9Wk~u}8oul*R z($wwZ@~Bb;Qn5w{#*KGovK}+^=?+A)601b8NNIc*e@Y0U&Z4Gy2LY`|HRvdx?gq(j~X7>1alR%Tk|9b5$E46wF!N z5psxZxu`wHO^UMkW&ujIB7dKgUK&Np zRmZ^*+Hq;(q@)zVklHI4Jk}B7BAqe8h4eTMKG#?GB5#77O_Q>^1CiKyft{ND{lsr@ z(e2M$9HH{oI7kn9_7{A1yk$}D1SG(dPC6mQLg$uF<5dOA=o&Mq<(Fg+lvg-=(`rWI z@9VG3cG;?l)83`#6U*>L@CnWMVbmB6+%1;rRPrZ|pgH;b`zcAFZ`HbzKpHb^@jJStM9 zs|LA%t#vft1TYYjMhuI#?G9aqq$Its0Hgfe`QlzbX9bd(J8;sKi!NFJq>uk_&ygj7;+wlr{1$*sw*Qh z?nFzOB<^aFNPUfFTP`XXn#=n*xQ`U)aJ~5GbIrrZuB} zwH5?qHR{zY%i9J=YJ~i*?x1abi|c~f;gTY6M{ZMXY(dZJ-Rb8`!agTpDZcV^qu2GI zs#clcULX6)EyYoFeY<2fL*U_OWJh%4N@X~$Gz2KUS^tK32N7Kbqf>n&Uwjixn`c{T z1>%X}SWV}24h7mUC0|=iQ{{;cfeOo5ZCxldp^OScSc=8g_LT+qp~CZ!N0qH`8X4h^ z=^oK{M?ys8HgA-GjIJX6h>3$xYYP>~G#!vl=0&eNZRl7iHU<|tAqvlz`HzBQoHpd_ zu($bjuqNkVbo_B1H~E|DqXzHn&&2)icUd6L9~8ST!!bO2jFb|1za@Bx!10_--qS`) zqrV2FL2s(8K;2Jilg&;Ng=o#R3&nDD?JQGyxjlvbIeTww__X%feK4pD)$KogwBE?N+_THSpq zY|b~DehmB6yj$HSeYtOIY&oFK1AJ-z$sTQGFpnOodq3(*GyvHQHj#Tak|851fc^tuAmv`2Hq zDEb#7IF$<>0#A~M@noJ#uIDi!3?!pMMo$uG2Z&#SWvm9`qm-VMulsgSo0Mx5wA0KNNZmP(1t!k z7m+XvT&u#FfLMx@nD+`(E_;T0mnMc-prn4{AHTZuVjD-{Tla%{@S#Ng?~dQ=ot>c0FW&yCFM#Cmsv_um<_WKqeX= z*B5K*()~&&T^}3RMXphzfCS|=AN{WLvE2EYAnYj%lDXJ~jBIZt48TE7oF1Tu?aj{07w>0cQ@nJf4 zyC5x$aI_xAMbjd!u`;=WXnuzKWxJn#G(^1qD5ZgejO9sG0&+IqA~}e(_%7IkDV$=7 zE-FkN+IdvKF1m;J;jk~HOq@9+%p%Okf57A#HylUHBgZq2YvL;~H}^yUQZjE6>q<+m z3oU*%YGs_`7SfS|uOwV^axS=n7!f($5c#rMExF=$6ZX~lWok1RH&dAJ zq$XetmSSM>eiHX$TMkU71_6o3(@IRXL&?vml*$=^9NgP?(-9)nLVj@B?l?;ZO zg(S*ErlpJz>Cio(z70gUm9Fyqh)+bS)I{+i7`H{cnKk~b7ulio^G@ZfX)Uss{rmEjwuTCQa+#T+T69-9lj)67A;we=6o+r1}?DxPy&El?@uR)ahQ58dl| zo<$8$7@<`LaFF@8w?F;*=Uds4wB@ z6{^_7BCiy2B?wmb?)^>EkEHm^v%+^`rUtseT&NVSPM4QQ%Gze@yFe!foVZ(HZAMc% z8!be>xG6;J&(Pin5w0vx8`4Cg%+SIiab>eRswt~5n^+TS&fA#lG+>r5>SpZHKQM>4 zTE{mR;VX$Y(vP;21_>VNqBMdoB{o|lP`O|w)CY0(!)@nUO-&T+N?R$rFY{rKf@^y& z*p^UlP+dF2lg!!R+tm5W7yVAy4!~C+ zKyZB)c;eDqS8kkn>|^&8xph8nF^G-u7dtJvEnG`#Cl=o6%2Wfd2c_@TSJUZN#UR_3 z^ZF=d#>YLrz6AD>ags$=iG|uS(b$)|9b1)$3&7=gM;I6LcW?GcQ^(jy2~^+j5-xZK zqP{O8J$k{5rqm`)HRieNEd@RfX?zboT6&q~fr?oK!W*miwf>VH*Xp1SoPvH3{jluk zz*$L&kEbDAW@$>YZ+%&Jc+cEWGSLdt__xA}a81Ub&=pmPPlSlcMWx0j->I>kiMn6S z)I=C&NClCv=%*>6nGVM76tDdj1uQb6a87uF|mEySF9e+pXJ4o`>$Vr}?A~@tcxSqyCn; zBMEOm3O{#m&=mxRD+QN^F<*Q^7q{=7df=P!4bz9@D^QhwZ@QdZcsL^7*bKub-;bX* z;7V#HAaXq12cUlh!x{DrPl|-3fZixr#cDZ?F0aAD6<~~GvlSN1#uw837M(Cb;*cdD zd`He!ReRoODXG5=vr_dQx$StWogTB3r3j$c(5x3{%_q^L06Ww)MY7R$PfW#*V%uc~X3>Rqm~Qf} z80c5X4V6imG*Y8MFJAX;7Ly{sf;OdQTIQKpPaM*4u@<`b6T3>D8QzRm){#LoQQ*kT zeqxp&WcCLYZqb`uKO77<7oe9Ji0yLv+TPDrr`u<=r>7LG zws%kin+mP-bYXwib9Eo@($6whd}nLK?k@V-secpsn_eQTt&0`mjcN|1bA1O^ z(0Uvo*vS`TuWLJVP)Fybd4#OAd3^9gw!WbB+QOg+Pc>U1dRE`(hZQ(~yzkTS1Yb2& zQNKy7DrkJxI8lU97|gL8o{v z9=~F|20AU;Q{5F={NU!}TDM|GS&n@wJ0w`C-MZ>-xYh7TXZp-`4D`)|gXocsYn;{e z#lM9r=Lsj;=ECh1U)>j zB^GZHdGXpI1>Wf;O0KLiP0AG$9(N)4!YkH%q5b#F%1>(I2Q8ohur8+iL#xF4Qz696 z_G8PxVp|V@GX5oj(LYlc0m}a_Hu`4*qkpC@0u=oz&-$qsV)&`I`g?)VKUNq0DEhP5 zp9Mz0Z0rA!8U4ae{uQ1A;BWtfKKa8>)j>(hdV>zRWmm-+E_UHfucIgkia^yR{&mG- zkz#SHfL_yAJbtmrx>rwbPf*(98=&{R5$VQ9olEX+A7vD$t%iwOc8_|mjteP=iJsfo z!C6_=G}7MXVR)h|B$}lh^&aic9JeCwBjBZb@6+Z2DavUbn4U%@x&!++)dhhEVIB#F4>J(;#(KhaCb=J zN{;%@*~c^#Cl_%R>uH$4_!}r`z1#izNCS7HSmFG<0=IKEceJ3>XfTP!IS;qoJn4ca zncno>-r@3_74Mho=)=kL23Ghk=L+@8??)CgZtr+mAiDFk*;*?;u0{liv7+XbT2M~n zvgYoy3@1azrIgQAktzF_7Qxv;md>3Bo9Pw0hnLF=73+`-h8Tu|)X$;PVVF@2S>jVs zg%{}9RD^!+4Ymaf@iX)tAQB>^857J5RM&K@Y!U_=a931(S+O@h=F-%`5YZ=hfuSmV z2gXHI;$!S@w-)5M)JwFMN}$A4VNa%dZPR#Qy-wS=t=C&t)@la}0ayj39*- zp|c%N`}1aW{*;aFhN#ZP`c15_73M>3j`@%`Qr8)_ybPGMD7WAz0S-b~?s0Yyw&)-> z1U5No&rE!Zel*u_DR*O$28s~8_?`hgko)#dg>3shE)?r_**T+OP##uOL%YI4JggKq zjdH=GVe&kzU#hY)L{eKhJ$Ej1E)Ka0H#lUHw0FfqMqMT@?9%M_E^~}|WH>HdCMrjx zrQOr6mUhKx-P39S8y0rO@&PYhx=b{VM$@>b$)8-tK&9s^K5*q$52iM1w>{==T6USc zy4-zNn4M=G=Waa7>_$>y-MOH^T_jX)jBp$2^Fjzs`?5ysx>7dt4~CTg0%l~TqhSJ2 zr}O|_Ap`pl)cqd(a9!5{&WDE=B|WcasW#!9OmJecK66r59VO}{t(m87+# z4k0n62Qiv<^3lZ$+n()_65b?&M09)eo?I44SA3snOm<^d5Jh#uzEGi0<@q~Od3FS* z=zgJmJFoQq1b)r6sxxASqEy%8<)+yxPV~#b{q3Adk&eHBMF+ljsCaA-0`f<4x!jBo zT3;0)W&3B?0VHEGeWkbHyS%B-T0{>*LjDVq<^465(X4P52B2lJOZfcsme2cArd*@U zuX4(LlM`9FwZ153rhaeDElkRcFOQv%o>s!o+RrC26QzRl>Du0g&PaX8&vSN-1;^sZ zhG9yl1LJyezz)tlvvF*szG{PeOJ&L;Id58%)06| zi65K!4|?%O1OGnk{TXHi2nqidW(0KQ&rLspyq{?k0M?Wq05k%&0*1uDxSG>5{L3)o z&uV_J|Gxn<{;c7*a(@K_{u7w-=U)C^&tG6hQBe_j5tV-kGXgS1{tjjYxb**Qfj_~F z0GjUaV8)-hCO=yVaO?SlTx9$^a`ETe{k~KGlDj!Stv;;*z~S7O)*L`D{$LlK{)fm# z#=j#M|E_KRhsedBHT{0n|8QXVWonMc#sKK$zc`%#HMz(DXuN+$E-D(T#+cXVeGro8 zdk+o@T!_)s-2BxD9UL9qNXJB&Pd%qukWaP=jQ_pZ*SILF4*~+>@0xDwH#v9v8h4sj zTuy6Glt%5!<3G!3L`ZxCH-%ODK(N%nw*+u;P6ka3sKo2)`v%H$Fq8h9+bO2_NzC#zDtm~SjA6xg05U|z3n=8dB zVD%B%9$bTK-nS{4X!8$>!ojPwp_qSk#ox9;3fi%#upvL4CcO5Tn zH$lA-SlWYh8YnMFaE^cjIy+}r{Z|40=3f{%fX;?Py`=Y`_&ZaUH5;{b&$JaTp2Y3! z#-QF}vgbr2A+PUzxh>{(L5G5BWOuPnYcHEa@VWYs@%pWf&Ohw9T+U)C)ddEg4$Z0H z%Tsy@Ceq3(3ugf)AJAJ+s5fB1vS>h4(0Zen#X)WLj92wYSA*`1fwT)4`!E%)7+?p` z)t^_M)-$y_ab&$QzhfVSyyS1LzJ&t?nd`u_0x~bahVi*|y1`_ex`A`g20+vLmOgJfXy}ac92QzEo8uN(+=E@fLJ#0|f|#j}HOC zJCY85?eSD{L1!*K^@&g!Og98H=xO|HSnN69G=ev_<7PLj;>}B{1d>ZOfLT1Y?o0*- z^Nvi0*n6dZT*ZHlU+^-#_j>!%?j~;^*zLtQ_ab<&4O4*xbMj2OE8Klz#rl}x(=rd$ z*%LV2@!~&u^_54 zS3U~k4GjwV5`StmiWknBjyQ{H_~OWKJ+rq;i~u&u^xVIo@ ze6PUwKwOTm-U2{4eO`RJKyXji&e`HP0NZmZiY>r{)+pfnrEdNuxq@WFSL)8>fmc5- zFSQI=S~5^;S42nzXvczgRxfhof|~=iEJU8>wAXz|AIma9LO89Kp|ay7&Rx|K8O?gX zOlsi?K?f>Jsm0^YU#7QbtC*vkwcg7#pd+&$qnGHDmdnufA&uqNNJf8vMW+pmGTavm zkzGb#ut(^zbjr0jYPwbpN~=5cmx4EFBAUg!8t@25M;wq;5Oa+M=>`re4D3~G-cxY+#E_jlczX$hx(8Wy^3h%zkL z>hpVuLrm}KbU&I8`LS_yiC?K)I85uXe)p}B<5xk!r^sh9nxD~muouC27`55f-dDgK zW}nxY?{^PkQVBIf9~cFbQm2N7SBbqPP0}CLBWLe&IgVab{~Z7Q!yD7ILigaAtN1n` zinWJP=Y&1!s^b6^7gU2hOp9%pM{I1db|x)E6DOjoVAXs04t!{IN+ERDi6e+WiK`u$ zTBIl})=LNr@-^GC^^~3nZtqKBHwawH6~mEIk3K=p8I`%)nV?q2JT|%@H0BRi@}=AN zLDG2}Bx*}sZ#q{>_qjcv7K`&&bnykKwDQH9_&N6rKwy~KSHD`4YA|iU3fj+l?x`HS z`xxD`dDS+HCa#&ZtL!Lax%4bL`f&=MNk`9K8Hx=xRQxz>e-sNUOPVsEGB?zjfC#zl zySz-P9|0y$lctjR@#d(^=T|Zz&E>SmF~WgQ>T$Vzhg?{iB#kF!F_cv<%UK0qWl-kB z%_$}%Xs|RCz7iTf3ociw2^XlvQsFL~neNG5QeHM(vZ|sP!b_wAAwi)&`KT)fSxgn=*VCudfWxe{ku_TZy2)G`Q$~1M8r6WX0{3=-6(>wRrdT+E<~>k`qZV%QlbnRk6Wo zvulL>5S%k~-H<}j;NDA{Q&w%%>z;%O4FWT%1k3uw7dY20mOm1bvFIX3H}0{o6t2*S zCk_`~y87frmoyRURl>jc=}k2QV>|03Y0>4e)Q*tYB@>8&rqKf$<2;tocCV7A z*{7ym{U&gdwlqsT3CA-s8{YDxM&8>6-V+Z=v%(3)f|A5YXkWc3sEK+I+k$vd7~-xW z>^;wTn&lQ@theyHRHY3yC2`)HixD5=3yf?lRnFRY8-0y4>f^$2#SP-2FokBWcvDS& z#QY>w)n3Q4)*XlXSAHJc|E!xasvw<8`_PQR+wUn)UCZcxG)Hi9zYXTgf3$$U{1V;6 zwX1$Mr?j%6W7wMS+4VBbhyJ8i+$jlp&puh;T<-m_F2ESVcgpvxr;Ixu@p3iu6APUH zSnhhhX_Dtg&6UwnOKh0HLQCHB3axCER?;N0o^M`WwgvexZok5;u9>ScXp~Y#HE9L8 z%P3<;Op-%E5lPp9CpURKTW`&1=2xWLR1e9Ri7?k#2u06wpE^5FUdV4{Fv!IM9V%H?%e4>} zG0dZTp-85~Mz?m?4kgI^Nuo*SX}(TtUjP1Zr)m1At3A}&EjPtKr?}Tj0xsX_1&v{%u zj5t}zozw}0qH4igj==c^%+Uo_dilpo*9J9NvwsxbMq3G{H%}z_IC)*n(WV8D^PC%USD(Bo+H%OazU>h$!Dw9L+l5k! z=9xq$-w=I@Q{b^1@S$bKXgYst|1&oz8~ZsvvRO3$o+i(KldVGk$^!_u2-FH)qwXPh!J~R~@HnLJXHw z1wDA~b3Clyy2g-$5BV5jQ)-ByC{K@oS@?uL>ka<2rBZlR`ij%L^I6Lk>9E_v?Cdo0 zpRg;wPfOQ}wD$zd|L@;F zy}LRCmwD9^9ZudQ-0ZuVo{nJKSa*ZxQO94jNz52~f%7_8%b$k|?yjHUD?v4tAJ0+x zbzXq6O;*UXHu$OJ=NbU9xPp@I+WP*@!g4T+X6u8^M^WZ!I0L6>*Nm2k+-GnLUbM*b zZDADOjxS<-U+sLk$bOq3HroE{vqt6dK+89{m-GTY;f^Z)V76^%6OC2%^YTS^B3ZY< z7qIs{W8fI%MQy47MbZ4#k93+w5oV?k{E3r|9_trV2J7Cx3r3$Q@90R9qm21WD=8T* z%<*P;;|D3?NAKPZsw5#E-6W8AoU7Dm7`w;zdGU1I(|No{7bjr`8R>WN%Z!0u=V|IC z>eK7DuCNjb@~4w_C_V0#$bEz}c7vTQ$LHGoD8pD;99J%JSj&>rH+v>oc?s4OW>m?N zrE%Aij}akSH#pX~0$Z|8>Reo|qMb#1x~|J3=#?5wTgf2)a&pDqw{|fY`{jDW?Iy%J zT;+?zX@j=%i;Y@OmSvo&urR}``io6sq8D4k z`ci0mY*G|S^8Z>aDGuCxoMWi}?R?Pr{9rTe8AtTA?CLO&5}&%e0%{mEi^fZ%`nX}K zPw#(=rp?SHou+ru1V3i(*FrY?s_eYDfYAOaMog^ zkt+Phyw=dwZ6ET?Om#8z>`*t#mjXKEU^+@c;6R zSS2R^VS;^&SO3iGEowTJx+;QO+<{cqaq=!^__0q&pCexI;0AR6xVHRhKq)c~J)di{ zQKu=r!(07yY0POfETJbY0ZsUG;ri&d4=>RUFeSH>k(M7(@h9L98Rk!*S?vzsEZwytk+a+XUa`Eqs0b3b`c&UlWE zpt|+s$4f5@rZl68?NA!K=Ol`VIWGkJ)Mlisyh`<5x-!LT@Yr>S(InV-^x4yLeYz)b z0|yjiCNTw9!MJQ4&aV^-nP&vfGr5HrEzJiMmirfLs3-ZkcaiG5VrX%Qzlr&lGsc|! zRb6M;%aO$D+L+H%miKp7>Cmjo1ck9!K5Ll1BwyQX{;+%>MVkA#u+qpAavBySTRS*X&Jzc~Acl-!-NM>Pk8g*z} zBqJy1;IPf;nnF<`MtF)+rA29%|80L54K;z1&Ge^*r#zpN`hnJ<-z?A~L!K7%1ygZ9 zUl+1BnUP}nGMY|qh@Dc*@#>ZwuUnaOAUj2C$*T%d*Ip6`2YrW<{+#1|7W7N8Pv7TW z7VPOjq_{13NO3FQd<^rca)DELm`C_%PN$v{fl8|shmlyxs zY)2(WPv?6Jv+MPRsKx1C90JOU_gZ`v@lw>?noI)fkS3HB;+bML6K5v7SEJo>#UBk= z*upKV$H}zqeFQ43bYI2tb*}HQs6XROH@-S|W^>fA|H8NK;c>3{{#W`RTIdXMaT6ZR z&Y|837wE$cyDF>+aTWiNlzv?dXM6yL$9&@P53xzpXvT2UmW4d@p&Yp~s=kDA1YB z<{Z2Jz^rq3dmb~x1^=mVLpMlW2^xEVLWVs~kCjg@&a_lHUm;s1O;t?+t6y8`Cvhhl zo6KfUUpiBTx9FdT9~Ah?8}do&@^=rPsdN7>;@pt7B+w?*sMywZDM`X&67PD01g^}D(wdmuikYyf#U<}1Dz*HIVS1eE!l#A4mgS&7ZHtrXEDhdR*md$SB- z7wUyhYG-|Ki^@^I9@y7O=Er8b^`tf4t)U zXv?X)gI9d)Kjn}%;#p)G)wrroyV5t0j%x+5;iFBWUXiI-4(XHJ*^qmu6xHh9Jz1aV zu==i*?5&mNMYBqEf*OsSKDh_)EtQoG zg&LM^Ym7F|*T0zjIJCiJKqB_?WKDra9Ii4DJgbxaBBni@Ef)FJ{Ga^@vWVt>6RNzj ztb~^RWMOCEy?~4;0ov9R+e-sWSMQCvJ1Y<3ghYzDUmb6(v5T|iAfVd9Ew0Y2FnQ$b zTo^n*W?QbYI7f1cZ}Gf)OqY^P1ru*?w^p_qsj&C@i6GsI1jy4XUz_YFg>>B+d`SXk z_(mhkw75*CLp3HdKVe@Bvsz#4Z9}{NChS90D}CBqDbj?|d%S&*2%nBR;?&#@zAPE< z)!qLT>&+}za%PPq{Rhw6^F_haU%@XE}?q?jCT`?~L92^;8*a z`d*0-cqiq*>oHsiFGjKc)__y*ZdM!Y`#AGmJ1rW^!<9SL4V_F=1+?^VOJyaKh?jx1 z8ZXskzqonC4CTowxm$%M38W-9$1X-N2f8S(W?a8Oon&_2qnWnts+8y%@WW4Vl9?Jqnt!AIdhOgO=lsGl{`5|f$Xl>a{JEPQLym&IgwUCxpB%whlm z;$etmqg_p$QY2&h{I%y!bBTTuvg@JLDy_b8!@k3h(d8JWZa=)(b?4iKVwaeLGpcQ) zIJY>rV8iDrMo!Pn`Pm!ZJ}I0pe%9zU!i(`6mcQ7=O^%fvmYPe5wWzJ{=oe`D9ll>( z{CtKEH!P-I*+hxwbWY8?5{IZPE1kxiXW67&6xZScc3aG~Q1%Wj+DQ zTXE@yQlj(X`~v~e994Wf&>{?+7&_QByyRg40`o+Ncx^p3I?OK?sFI_d7hZStGLhGx z#tNBLm1V8FSj^ph4JGC!buQg&QjeALSYMaeS+2f2{Y_tlS?>gxm?<=3YpxoN3%qWb zvnJdc;h2+qz9fuubB5>nBZnK?`Sd4gdrdN~os5>jz2)kZEbDD7cBU#!NFs2{sMDZ#5F_BIq{Y^9~5M69DHD|f=M|Dq!z z^M$P!+mGpXLwnwftfRNwf**L9JeIsdQ=-GOjA}pZPEg1!9RK!VLQpF{JhVl&`(8<- zWVpLlM61qas~fnB)l!N(EujyX8AujE zrXCcSIec8eO_FFNPS;KM>J(k!>s*Fi&z|ivRx45nrxDXYv%h^STl{`MB)g=x%Av~we~eS=hL-BYx5-K?m>EfN zQ|)urVY=xWNelJ3!m{s4TUYLvZKR@US&eSG)r&gkOSK=$oIJm*Vw8i|;Mm%akO z=1mjffNu`-&Z&PZ3&PH@zlYnxBYSe7g@nawa)FhnvWVKFEXa+d(IrP0-C5{1PE5Vd z?9$CZ@@_d|bFZ~nO~qS`5=yL+XB2nB zNLd@Azjxf*Qkl*necycl9kK8OjW%cwQKYO&sVO$w`)oT8*QbAp%Q@e-aOe(QaCj<| z6SPz^Rwd-CH#jvM_eHeKt>WdlzP!^8mEyQi_SKvvJP|xB!$MVebfF5OBHYu@*dIg9 z-Xv6R8#bI;33#D|^PMqkVHUly;Y!=9?VZN#R3^?Ec?#I|G=6$k*`Pd;xDm;Td1@Om zyDLAa86T0gIP_96(CEi62BG`I_{~Jauowj%nFkih#L6O=6(W*^BN-nTX_~}$eI%=@ zPHn(YSR~W0rg?d%CeR?#`BFw?e>E9eX|E+x=9v}zZcbKZXN_~Dn0(_ac7 zPHP3f*^%yyRoUpDH?R)*YS??0E3d3VJ{&WK6V1k5r}laj%Pm{MM)yj&?R8aS@rn-(6g=pO+98}1 zVP&3KD*07gYSA*3{^OS>!k*PgD#`LL-DvI@NX<7h>c!)ss~K9h8V|;gyPEh=Ct?Jn zl&PM`b(Tb#`aSnu58+Aevc|)i{EWSCBy~CKfmw*& zR?r!buH2-nIj*wm2+eK%uNNKl2E3TMXNK?jZ8x}5%E<}QkV@1tp;!mX+2p@njZ8#Y z#~VKr_a$E?-gVpYy&i0wEC9b}Nvh+-Q`TmsHO@0}G<|kS=N9^3vpsYn>eJC$Cejb= zRWDvWW5%RY{7x{*xx+2nvqD#%%c7ykedw9(Iguaz>-63)7npiKP@KzSmLv_UjgnLk zlB;UCN))}s*(WS0dEK+w<*5t*Syes6l)B7(%ZRbEQ@VIv?P}e~P6pcR;Fo!iA3O^2 zdv&t&jV+xtyp4SOUi3f%l}#E{v{*XaC+FsCfwtT{zInX39`bK)l-zM$VEpWV31 zWmUIauWn@$2ji>IReO1H{WV7;@%Ik|n#p9wna{XC=g#v%N~u={DB58S#5WRF9860| z1=hM$Tu$4%&6g&;R=6O!Gx%hN>?3)~sVjj^({a(SPdoEi-(7(=;;6e}L8={b)-Ou) zqWWALH-fTH(PTlX?Y0?&sD$#f@-*6Kur?Oqk`XVup5Qs(zenuKoZ0XqCPT>V?uXT# zSVUQ|_8pg7vP7@T1YE_8Cmkk(Q`}3vu&NTe8{b*^Dy)Xd$j`7ogQBYmiyz;eoIZ17@v-~T z_fy?cwac_Z%G;qU8N^wJw|+c1Gu(HN!C-R3warJF4_bH+w;-r0G@IbnJ4=}&f^{3~ z{^s(VH=kpm5xsI6$Ve6|YL~lsS;NXWd_cDPm8aSx^%z3*A)M9*+6gKuRAT9y_t5cs z($t*WQv$Is7~P~fS$<>54zFg5;~lwJnu?7(R`IoEy(yGv@>B103Dry5)|R%T#CM`@ zdNqAYzZM_jBq2YYxSEA?^V&}9!%YG_Tj?)l4qwiS^q$_xEBJvrE=kVtcN`1|&Om|^ zwD!BPgALM;Hdy~R9oqrC$A5~cJPN2hxO^8t2{eL6wp!o)@H)cHRgWGUJyJJva zU*%l+S}6A579oYcWKLJTR8QzIHCmGN;JF+3$`VfBd%WTP%v!3;f#;R|3zMyHyj<01 z&IRM>@Jd}lIa$bOhn>Of2yJa4l6;G&tnK|8=TkMSXo&*XW+C0XlZr8S#c@Tau5Jk2 zT1K1edmLfFthE9aNi6E4CVqlD5W?^rKRnB}UgTwFgC1k;bdT?_-h%)V6_QxB>5y2a zROQQ93~x&?)WTo8e~$Ca7J2Is_!5&Ts=udrfl72rRiWFEV(IacwPAccB5b6uZZV{u zCuF5}-NtWqS_)g$_40W(6&kq6Wj^edLDbx7EOTN%N6C{vzK;;bS;Yx>ezMf{Ysf>a zi|D5t_@?Wg*F8m4!LMEU$SB)lmGYrN$cJCVQ>1v2@f3*@Jc=u1--8AoX_CkRA_#ZU zcDqdyk+?z*5Nij>{|K&-eb2@Ixk(~8&HnuV0j?07VedMm?g7q_pI{)3zdC-j`QRb0 zP)JHqN<{gWxI%6K(}u(bio$IT5oXqQRtSLCJ@DNTT;Z>pB>oy#$N@G?JVxxXX2Jia zBceQjDYW`O>w?GuA{}?xl-IX_Ls*+<_OCULE_GErOYH3s2<>;z4EYQ;g+ zlc%swyr~}5!?QVu@%AiAz|LvWgt~iy=M+kq6@!RUgN3UHPGn%x6H#&ue0sCvl3e9+ z@1mkS0LHhi;b!1svm9(`aUrAZn{pCex zXXi!XXM;{krmkl4md}X>22ZA*we?Qm*Dv}@*ATVvQfp3?|Jdmq6ddwlOHUtcMTdEv za~#-CbJzb$fF||ZhoE;}@I%wdKFgulb?S4;UynWSa{f4BjrwDN0uo8I?N}<9?@8!I z(mUY#7ES*VGLqL360R+!Twk>455$TQH0M~rp~(Er#lsVw@(|@1L%Lq z_-^LprC#>$ya{CIaU5+N$LzhSJmebJ)0&llgh}3)jb2PIj~(SmId5A zDRT+%CwyzS4g23bDRQV1&t0`1VHd8x#jkfe(DOyst-GFXYf#7M=P*v;pFV@CpLIQw z`~AIz-phUhEIC6$Us=~zFM1FYg<-YNyPS`t9KdzhIjJ`QiGx1B`Vn=~|6AK%Rm>-d zu?u`f2Jdk}U3u`oDy-h&Zk|886@A*YguDHsot!ZA+{)~?t+u6TlOlTR>M6>N1@Fnw z8(pe5+NU;Mr>km~mgub@eiEm!$47`zPQC$>?(_Tf7}M(u(yz~L09ZnaEYeQg)91Gm zM`mL;)6LohqyAcasmOEhM^^H~*Tzunw~J>-n6C>%rdP4Hf28azg#8Gbt4iAz#P~79 z&)^C0C<{-;>+3e+PUbc zDv_K!Bs;EBG}PzeTQXML!qwL?NO2ZVYE!Nh(wwTjo9%0cwfwyW4xwblpYVu9DW_;X zYw9nI>JgiEpZ>AJR>W-a=}Kc#$}g15(K&9ZKcK$%+~%uVJXcSwUQf*C{1(H|i}M}+ zqw<6v)Qb2#ls&*p+J~pdx8P#cPuY0Ynvs{GLc-um1Xs1vv zek85^`>nMBnyJ3w3HqS>Ds$MzIE_67*S*wnW`q~64_(yY7LhP|{@SGd^Vz3RM>d)O z0T(eLFVeA6l+on@<+e<0#Dj;JzF(eNDAYY@i@6ejVnv=HyR(T~QxUUxEs|xTb%wnK zMMc{AwfBijH>O^(7@KAoUP4j2lH;DhFy@dbB8QT;bv%*gQs>9d_o&~I#apX9JGYI?FlX^1NOe@mPT{>k zgDLvkxO-t=QH}XqR_J((sBTvT=CM8CLP%_{ufvyHh!W6 zT{~1?$SmlpG&+UEe=M^TQp|NptUa>e-)4}Hs}Q~YhK87eVMKHA#*9&_tKm_9j%m) zk9%R3FRo)KgSCX_&{thu>apbWWdag}z5oZTHW(-98uS z75-YjntG8#dI9^~wZJH-C4_TyJaV{k_Ss-;Q>=>D*h(5iRqcn}M5BC{m`Zz;CJJ4; zewrg`<`^U6b^*6HkO>(CKL zUTtq9tc&-u`i7=%*sYxxSR`h*T>aV+B~Xy3jvaRK1;Zq7#mejLH-z4OGO4rg!VpvW zmn=!y!=I!(%mp;nS6vxO?O|EtKp&5pP8wIld_Emwb@FU%rx=#NYUXf-N)7us#b_Je z&1JFzB8yP8umYLuZxO?Zt29h!*y_49r47opUQu9?4SzK6cgWJn(0Vg%cDph)g~gIT zpmMcUd`n5&d+=mXz*Ua;wRsLnOd;Kd1s?MPepg!f&kcb-$lZ*-Smi+4%%MZ?Db@+FH~l%Y>?t^BCW#bqgIE z)^D_{7~qVxSf)H%Aay=f;Y;W7#re%zfyNF>!xhT;jfLh&58t*`!Ta|_p~Gk&xJ?Ip zT?)BSL#Nivtnka4O8GNnM~E*nZt%}(D1KpIrw(RiTo2hysZB(=JZ}>EbX`vhVsz#hV77m zv(bz%ix!)43`bMOqet5+DRQZ}ty!{JBK7m6-v$;b`uQqQ8NUYP-A?_YIh8(Mw7Eh% zQg)vDUO8%_4W_>lHSSl!f!f@<2YNT`)nxSU$PV4Q+e7E*>W`yjRgNIs`7TF%<3p5s zEh}YEfxdQuA>B(%%^e2fPHtmIpi82erctTGayViu<7tnPl~Dl83j_I4Tx4rQ6!nGM zOy&hMZa`~8voF_)lvO*l9;+{rptD72|qsP&AGl(m<2=O?Ax6%N)#U=$8`@xlJOm9 zg-emEa*}?yS0ukB7loZtDTE}6=+~WJV_+GG==Z)^?N7kgfO9SQbCa3|Eev(=jTfre zL;#zP1etC+SylGUyKa|dKF)lyLL?ZzcAk`cAinUjofcbFYWNvFqcl3>DuoHKazW=M zh(hc3R~S9dVA0*7V1zKQB4rJsR#=y+6scpWugW#jaE8hnfBD2kj5OLKW9=M+*3Rsv}TFpW? zlr}8y%e82uiV*0vAvi-5U%!YD`ShtK(-f__t@x8bAVL5sAhVsId*6@uIS!tip4Psg0vk4eQhw&?_}U2Ya1G z)e`vV`G=ppE3bI$@X49ZTaec8g>wl>o{*B6-8yX~3h&Ri9XX@Q-PdXpEw&c8 z&YZ9|rb=iZYa;LLsPJ;VsoVXR)L{N&naIu4-%n3kaNqe}?N3*j=pr5HXWF`mC;ZV> zHY4He>u+hNvVaaRe&hN4%T*RWOw%N_MJ8VNBPhQ=Xy4Gj$1OLdmp-tx9V%+2JJmiv z-1*3a#awYUHz^Mxt7+-!XnO6(v}FDDXGPbnJdImAu7B1VjORlczHjtmJxIHa?e1frIOis1NrRbuVIGA7_`vG7Zjlw=Zh9qsPBN%Ues_CL+tSs4bW5Dj{mXd*i2WE$ zzCn#kqsHI)Wzl-D+w=4eYKl(xOH9yBs+^Lz)h^FuzDN{KRo0#{ad7DGp-p(! z7*>zH;PZ+iz2H+Wc}|&`!1v+AN^{NYPyBmO^9Tzmi*>#Yo~NESzf(p(=pOfF9Wp1C ztm;Q$A~g}!Q}Vo>$W+W|)3rPq=UE7U!~^@edMZ{-qr8W8W7iUbDJes^TZJq0J2$D= zVskMcI_uEMpUzAbXI~+^FFE*PvOGQ%3x({mV0f%3y(wnU1-jzN9+Z_um@I34cL`fX zF`L$d&iIw*s)e`5BAvq#8hu4(!;WXFhLz(&yvPvTxprAM7t02UC@1S*qS@)|1dm|G z8Y@1FsWVDBokq^+=~GW4-%?pP$GUIlYq@K%(*)lf;URuN7B zW7xu!BXz0d>z~L!wX$~iV?d}cbup*shQ3O;PsQUp#LQUbNLP3e?RW1P)sZcHnzNxT zpRN@st*qou2X`V&b!g@>ChossWZc5{$VtFg`hvsY0sw0WS@WDyKIrklM2Fw_;psqg zI^~9Dmt$>*3DDKDTB!Ut>4Mk{mB*v<>K%(JS#>f3isdP3Cf~OPeoU)giEJpZd~tny zVr{r67PU>J^}Lmqg|oBvQ|{+hv8or~)DIfDw_*b`>S_pZlhb9@S|2IA=J>`YSs+N~ z(J1o4lFr<8QIw_R`l5n1j1&hV!X&qy{aSo*z+hVVDrpH6|yg^kg3w9+pl) zlT!>7w3ADrQ$*)A(>%+$>&|sWhVzS*eZ0Pb;xuos-|g7odS6jQTu7#sDe0Z4?zhL1 z1}V31TBp#+zR{c{A8G11lQ7dWJVRLi_EDhk*elff;MY(6Qx+D*@t93`jn_{DzdHNg zoQ7%1K1*;V=i%o*udnQh(!rbL_al3dDlzm@w@Q}*K-XkcRIXC<+xcNyd~HT^oglUihgcO(lZVfW;G6SqiRa|w+I zIu;`<<9kY?&&OO{p7fm)@9XSwibYqt14Vp&7y4jaMZPh-)rQvgnuR8}ElU+eSj6U)dZDYP4feKJ4W^kqIN^^TOwIttd^#dI5dt;#o}c+M9B zWVO7cI>KWPgRLc0OWKQ)TwhK^D@?f&UXrxV>l5WDAd1bZ;FH6=uv4uS zVP|!>#L@Uwkj_ZQxIl-4rI7!t4xI8ne}b}Hf)sZhBDu_%PW;6a3))?`qFmFh^L>)g ziqF;bS_ccE0=K>;Qk(*{ngyvbN~?+pbR}=S!qhq8Lw^7CU$Fy94ml*|5+8yZG~M(! z6dtJ-Q&d2v`ZJw|wl?^i@)Indve9mDo4fJfMJsjDZtqgsXsPjf?S-p#TP9O{l1A$B zlku?Z2Ncf>(kVYDW?Zc^RafFQv+B7zzBzakUBIky?84WuZ}n_9zxjQM&b?T#&%toj z)3y#i_(|W089sLT^`_F8)L)<7(#d7LA9_jwF{+5(I9W8NN-8E36;q z$dy5j*oVhtpkVPP$x8{EjSnn?{?qz2lQYDj>(gx~bu@>>Le3;oD%mEK2WkmX zZeba@L?`9SY!#;}ixZbmPSc8e@{Ef0ZMgBBo9O*OM>o!>wKhvm!@Gj-8Q!m9Tgc4c zJJ_45L?Vq|;!TzMLFK#ziQNd7>&vR#kdI!?+e}M$izNg$cF=>alvm20_?T_7+#iOA zpkM7WSLcv>PaP}yZTRB0Q&i!$T-SZSP~xUyQ{TT_1jf@X;Qen~%AQ5=m8D&%h`Bo# z$H1ST_n9bb-61aRMfE8PY4f1Or+OkNU!}nMX{)72hyQ}_P;=nRkY^j(bxES@qHJla*XD9>r`a@A z4_?;n>F+xkZ<;^*qPWIJO`9*1#4;6DC9Adq0_~I(4 zCca#lED9?|*Z4g1vfx^M-m3x1QJ2tBQWp7H$20){Ur6rm$>h?xG&()FfpTRVD{3); zhjSso`f6sw__ZKfDMj6@ilp(CwXwNMa8?1)$foDU#;~>LiKTHk22s(?QRBwfzKA`3 z=n*$1@ZO}iezb|+3Ma>*vX9%=@!A6S&NzMwaKn?>sfjUwPy>VuCA{YBER+2rFFAyh>bUdaK3a! zx4&Xu`YAcJ=5p7A)!2>Gi$(K`$*5)8XBlHVZ@fsm8<~SDcUs%3(?U3_`2}4&{Y63_ zp;Pb3<<6Bx)uZKFz5lqCpM|NKzj|f$%|;`AA*DmsEq7{xN$n6k4}Sg-Gy*cg=w98b z1P?meLek1;mtn$b)d$j86|t`{)ukb=+;3Sv+%9B%+;y%*qo+)`fYXuitH|AK-df#4rxJ4_rZu@FQ=X!ftwv~F((q)Ga?F@1zFUrKsm%S;=eOle$~qs7B~&X< z>*7=ryS@8pUR!a4u2ZR?P^96Bt5Q&Yp~;GY2b^%!Vd9m8m~#<;<2%bmQhaeMZTUsS z<4^uj0!l*dPj-H^<^0k7ZShw{qEo^s`_{0B)44A=iWx1Jt<=J=UcTb-ZTa(VNa(nNuVHYd2nSJ(3n9CR;bFStFa6P{Z6^l9eHXIFcwFxH3W z4c%QQ)@y}${U@$_IBvq`dTl9Qd@U}vk6cPVz1k%FO(FFxIZ?ODN`L=}cSEsc(^d^L zcGI)3gR|=L*5<6$xRNmFxhBz?>)M$LJ|jkY<|^iI=ygduYuUOE^q8RGl{$}>s+hW5 z;pKHc!TIb*^!-(RuFMcm?=+Q_?-Gftkr=rB%OfF%^7n3+)`rA?*RRJjPjkVsBuY%0 zYd4^1wBpB}O}cBW@lm4iw1=~mR*R;fKvVS#b$sg24broUJ4T+XLO-OSs%`MFdbuxL z#i>Cq&%`HWXw0fjR5HKLsW($v-SoxOT>p4c!MCU&U-%2PKkBnLy7b@3Lkyz>ReC0? zrDX@+K5Z%M=)#Bdw2cYS6_n>QW;V@DhYFpx)&6rxgCNBN~|ui z$D&yc2rCR^s((;@SM(_%Cp<1eIs$v(Y0QOXS!{wtjlZ5|RgR})@nrNctN&%G3WM3a zun*46nRjET@|4Fc}--+OfO9q*tw zR@GpeZ%@kOLCmfl^iu;DFFh=Mm;cav+=D3H(8ua!#$AezO^*It{ShX^LZyfE{!;HP z?r3sxpx)H{`lx~e2aD|4aDY^&&$AniFLBTM$#F|J*yM4)j5hs%ZxY>z?Yqg6GuBga zW6d3%jPi3X`Q4JFlT%;rSMT%n=y!<7LZUp?Jx4HmfySBVM4h*xEsL!Vc>oFpJc#Kd# z31+v@QE4S8yoa<4xZiVC-&ZR(^tXTi`Ug7JBQ>4B-%JPZ@FMSW0^lTG4i?V+S120` z&$0JMu|&H#q+>uL0NwC!u|z)uiGWLYaXtTxCHgy%2)KCn;II3$9mNtI1ri+shd7R* zaQ>eF4sjd>u^a-2Ku&%F4)N^aAO8OX90CwLFc8I~j06Gv-=c#!jv`AG?Cs3WERlE~ zj(uo|z6o4b-vDk79OKz**Q5v>lS4jSl2z3fE^lQCyg?oUMsV$+QNT82zyl-g^v$hI zjzKAq1NH$1LiTp109=C}Xt^YUfa}|tSy_te+rc69qI_&nHV!Bcj2#ArLD`^8P$(Vn zcYhr68f|)eOIvHWp_#E6+(?@NI3)vjcCo!afqy$0YV?)3}J>iIy$l-z;ovM1}uhF77QSu34k~up-jJ`LB-6@9H1YdMg^g7 zZ4Ea9^rdfZ3qK|Tq-6I+AYx??JYExcMkmnH4Pk2sJWq1Zc#+T*K%Pje2b8`m&wcG2 z{Wfs?@oxhfIRRi8JBStp)GcuNO$|)|Qpf^yeBuJ&kG#Ar?A%bKF2PQ6yVLHec6VCN zy-)IRCnQOGXc|R*1oBy+`=$Y6yLS22X6NPt{&8zVp->p|MH|rGul_HFJm`dcfg@iG zkuUbh7dzyO8S=#e`2wDWAm80UzJNn4kng}3edId?^2M3qm@JTF@5^FO1V95kP=bFD z!9HL6qTf^E-J?g@j~o-%NIebm%g_ z77`aPl!fzeKC}B|UGP!70M~n;_IzefWcxze^P%7P3@Da=HZRZ@K%W6^#~LXc_;J>b zB7*;@cEDp%_AahUezf;{Ijfd}kr%o{>d7!U@B%32Yf{qNDF{nxK9jHXm4?uqb zSp$^~o&XKt&paP3W_HyHv>N;)^K9I_EU*LS_icVJ*6u~#y*PXznf)NRYvQ|d`bTyC z(cVFIIv*G~Xwjfp4!Qr&ZTo1M^hX)M05CcCfeiMnb`QjR!1SIK|Ir-&rSZxl83m&! z7}XAAILNIc^6a7hL9_WOxF4-k4mjH*@K|)`U~v;Jk;CYcD7L?5)Sy-mgmkoq9R`#J0+Tp@Ojt z3{RjGz-T3hJPY!E$R`;1z_YZ-v*0`BgK^+l(Cd$9axNrC`$_7L@_=yxOV|TP{*8g} znZ>_UJSUR8-zi=cc@m@;Oh6z5Aj@DV1HBnM`%{_*qa7H?L9+m>8t@B1>HWm~FA6yr zbdQH)zJ9!z-7|*$P<^~!-LFpe&i+Caf3$bduR*Fo7QuHQ>7Z?c@4#cAC_vsp+W^%K z4grrHX1qi50i^|w1Ftncuo6&Ehmr=*g2(KRxenLSqHo_+V0&!-v$Xb7{=VTHEd0QT zv0v={LRuVuG7^;oBLN9M6d5Q5kds5h0SnpV(VpvQjdsA(UWs*_CoY~NxqQz8_6Gij zr{7z^9}OQ2Y@m)0ReAXF7u7h|(Q52(g(mn@sDHIM16n78YnZ*tWbe}+t9z;ICsq%u zaZr-TCFI|OAJ?BWD1|%;G71{xq49yb{H@W0C(MuACz9ZwL)kC6=i*^MvNYXi2^8ZW zIAB3i`p0z{SY3b|fPVT@cmj2}OE>bHKu-jt)uG#hL%?gnAO{W+K@L7#uz{iN4)WMf zvFvC?_BSH{x3&JA5$xseJtO$3Qv1nkckTZ-I~)hx>^QwimKtumkPE_PhkPCthH`W_KqD+-^I% z^#Sbb9QxQ^zi${fj2$^<@6j5_oC<~Pz8YFUAi^rZrW5en9X5!HF|erv{IXO<79ysC zX@kka3;qX=KYY&uNDuf*h@l1MPn(eJ5K${kCE&vSDUR$l@vuNSxY>{wAx|U!AWSfU zsrQFMA;7#AASG;2Bqe|d0XjI?xq+F%%%)-lp;uHDmX#7=fT-Mts4zm9kDmo-R{G8K zKyKKd{@3)ev9WOc1AUyp!(2cL*?`9>ASncX$qXEkg|KmPvv5K|T_b4}LGFkO+1kQw zZ7qO(vIAm|5BmeL!0m^9T7F3^(w+W1fD_8X^G9xgU!NYZ1N)gBHlU6?WXH-FVrL34 zgfK9(L)cqD#Ptytwuej|8~l5wxPYHdv;FrZ@v^b-avezchJ`-D4k7~l*bkv^Xm^B| zW5a$=402((Ps}eR%<+!{fJgiMS;9~Vpv8TQKJHr_fU)s#K#|o4(q4cjarVePUiK>x z>j5pljA@-^JJvlJm!`XgG4h(RYLvnyAj`0*Ze(j2xK*5IHj39St&I zEIB^p5Bvc?mEI@+*Ze_w4&2iQ^FJ52f%88LnRj;z4y2HNNdz|##{rdt18jhh%LW7X z2*5l9g#gOll`;D<83PZn;(!_$85-#W!iE|17_jRbadL98>%)1WhEM~Zf0FUx6u%Sk zu@S$Q@KM^8Fd@j_3h$+^{a+1pBc-}uN$zDiHcmFq|0>JvXQ0DmwZD=8lhqNU0z|j# zBkf z(8C}Qh?o=H(B96>0S>ugZvjNuKbRrVK7)-NNXkd%J^qcyGy5JHhtuy9#Kp9{!uOpb%c(|AkdHfYrn73{h}^BOJ`&z~f$aYl^?k$Fa}s{%dPBr~E%)6t+Jf^FJ^O=F z^ZXyM#`b5{kV~mwmKH~|)Gr6H1LZ9{P@*CA{>uSCaNyb>aJ)kLW1i!J>@g42jGQbS zZ0y{~%|RghA(!IJ$Z{K)@L);G1viGW=^Fq^nVl1A3^m}?2g*kSC>xxUn+s-Oyth_8 zmfe3!mq#Yqt7vwU=YDM4EgKIh0S5ieDml4XpkS)}m3_c~=nj39=8gh^f1*ov%CSMb%dGSZWKh0 z*!?o#8f`Y9ViAQK0@VpZ-`vavfT`>ch6qUCEm_7f*B^guiNQ~Z=64i*gF<+Kc*?`Wi!?p(^nu;M0DgElIN`=zTx|MAP%ch(BdCEP6mD$5V+`fx z1q6=VBl)FY00DM)3;XW@^S>!P|HGp002jp0v3p+)q~bT)+$}6YcLfW|eV?Egh3g|A z`sU_W_RZ%&dHYg5sI$3v_5etKXP1p+gx+eehD6p?w!5G(TZkd@ zDBw|MmJpyf*6t+80m#i1a{%22Ect;uD?sH5j4|Bb%0LeI$+h(j4FNP8k{vs%V-z4Y zbF_xPVQ6Iy2S@Dd3__3O-`3t532Otz0}zAErO0p72k5dkvjnhq5a2IBl^NntBnUIx zJGLzQ)BQn(oO|ezKiCQ~j2<}uF=JtdvOs|qCb0BJO7TENW){}waOBn*AasZY+z?Q% znXLtq6(ciXA!ue`4`2fka7TS4$j=I4W@2Uu6bArH#t175;F8^5WJs>W0JactVADz# zhP+&VpCM3#^vIp81JNUgA4p!7?ceZ@-1Rz+k{a5C9Sp0BqV`f#?HUk9L3%%pKsChR$HkZ-}t6wPgkb zX?EO*=`DaO^a1&qupCBy1l$CWENCL;=AgOi12zLH1egu5)&mmO&mt&5?RCFf#}27;eh~0d#k; z{@QiBT|FWF8v=MT#K_8$4jBrq&Gij|l_}C=Ar8Qb)f{kreOr435PFe83@IQu;M{-+ zk)YduBPd|o^Pg<)_kv>KhH-In0F~JP*WQ^nH*#d>`g{M1`ZCv$qe)?3G!r*M%d|!g z*;aJR@&~uW0TLh^76{M)DT@B}_jyk)m6=r_L3WQccY?D-lf|setjv>VdC!uplj!ea zyIybabSjvNj@`bxTk*k{0!+k96B)oaRQvn=&GPEC@&2nN@y!b}MPD01toHj`zRNG1 zTg{9&i8IcI1U1oT%1<_&iE|N2jYzHwN!9|r6%=1 z4B;lKy}DQ~7iaVHpVfjLN2;;d$fdpDkMm_*Puruz3?IxWE^4a|Go1E6;uyVKZHUBP zuJ0wScKaig2tdv_Ui*Z`^|@|uFv7Xe%Ew!*k=$O8Sd$gNdeu%ZkY%qhK9z|y)pggm zQx~1`TaOdp?OzK~@;Js9O}Ej< zm~LM!k#N#pyqbcvJ=?987uDwWYPmD5w3KqvbC8J+S$uZ(%sA1KB&k>WH;&gpNQD_Z@+GDzkPK|f$BRJc{q&_$W{G|t@OHjSMLG{ zSJ;Q_kKHQyOScN|=&|2F_b9%dy%4)PC>K+DIY3agD&5|oo0@nfrG?*rXNnt59;Y~& zs7#sJeALk#e zO_xvWxO;MlQndfzfwhGc%!wSsegN|9P5NUl>M&?nE+OY66NYG@wi$y!b9lpWQ=ADW z21CS75%7y+=Jx!?Fj~;Yy?jx@+qK+WQWZjg?`yna=WE<;@@Da}fbn1$8t~Ww941_; zy8Ey?{~&OBx4Zxk1QZmUzT4hje%OK;+tgeHQv@!XJUgxmpBz@Dl_BXis-TtS@d&|4 zeaK>RuJ;tF3vbo*kqpo}gE4VZwc3~2j?cjnfJDI&YKP#1lN)16h2=T;_1+&_+p58q zJsr%b_5rZradLT|T;O|kH{Vy+%bgsKY@c&72Iri4Y%W%7Q1I1ekAv0dAv)kJ$Xrf= z?S92gF87t(C+Z`k7NBUH?0#_3(_(uLRIzd4Za87+-aHW1&HRjd98@m>;1FbiS4e&Z zPl6RRyf;W6=y~~zcZDQGX5Nmw56gV`4)5QCmjEkx%zbeLH9i_XGn%Bd5OXC2Y7omc ztc~du)ChW7`R}LH+w#Dwr+^ASg>7pbQb9U{-VAIk28y+_t;2|g>bo55UlFQswed&v z|FvUDf0^$;92{oYoe^(MDDd+?L-Ah+`;So6Qx_-3uN_|bX97jNS@lo1NP{$Ip)p8Y z{nMO7YNVOOfl72BAT=Zt=W742TT|%Ztvt@TNANGhL;y<%9TR!iP zaXypCiNpRp^JBl;;LgM)`IBQ5E&Fr;BlAF30(3>(YE$)rpN#kgY{g&fkUQjyR4_-q zY7l=(8VtJwM|Wxve@Pm6o{@~7{V_w-N(e4#*$%oXI4o;>6V zo%SY_y+Xva(~1iMdZW-EI69FJD*}4%tG%TO#n>9>dX@JXxqy$^ZKadmpT8OSprQ`p zcc*=KY-)<^m@qs4u-z{=PNF#3-i!Ohbc6T6A^WSVK4pi+9D9~n>QiQ z?wC+0{ofv*BJfBwsLFo@%L34z%2*?QOX4d$Ggj-|E5Azxh2oNYtcmMVf6T>cO=3WE|9} z4kGYJ=#%LH?K2qp(kDhV*|8cxCph-xi;`DrSoO$Wl!Na9zZBnV8s(pVmG+JzW|3A# z?qP2a6FRlq4(YZr%#SdzF4>`vobYd54AAFOp!FIlISRp%tfYSlmXF!xwa0V7p_0KbiWK+Mhn4IWe4Xb#Vbd@<-8;0#}z(E%Q z)t{*{BS)I^RyYK?x3vkD2P6EnJ047R6dx9CKbk4-W*=R!!3=kH43V7DP*BK%EfOW% zd+HF9+%%xj6QN%UUska7`_F=X9wj89gBbVtbUEROGl(iI5njjiWjhunDIZFz3glCD zjv&n*F~N^fURbJk(LonXl?sO`y2wfEHV5&iMSNj+)gEcIuig+qMdLGwo z#iA%wYB>jo@KL+*ZG}*6&Z$&MNMEu@wkjPSFI@cFeR%O`q=MM-M@{huzMhH!Q{qv@ zrkcUD_IaN$nl_Mz`|7#LeE=Poll}B)+Me{^=-mGV63byAiTDo_K78CMpajpsgV2uj)U%8OwKz21D*0gRajFy4W;~0jVMCVydp3_>4G+jJ1BnSTowD@v!uX?IP zSPDY9B47~t2Ar|yfD;0Jc?rKSNl%*cVAP)&0g%zL*@K;l2D83J)!+AtjSnE)_3hbu zb>7f}5h|;T`?nTm{pFR)liC8--`a^0$ge}hh(J1|ueD~Ju;NA{njI;z%Ey)%<>dGo zDoB3$>pAg>j*}6czPci28@zA1|FB&X;`k%II>{Jb@?UX9MhtP3E~Qap+mH>;6lxb( zAK9#eJdX#%{@QLs20Zb_e^Fa2Fa8U}sVuc)qUGwm->3U8|)C?N3RWX1A!cn ze}H7LTx-HBdFsX~w@y>9!3O+^DCgzE@u~ecAwdLgNzcUJdu?bhW(9-sDI(vzd#Cph z<9j0Ci97dg*OKYX*toFyHZ0~nDlSggeYpT%U<9dKuN`-D~J|^jA0`(kl76j$sl+X)Affgw2QoV0se_ddEoZtnOw=LYhPAn0M zVloSnLqM8eL+^+orAcMGEp0Ho!UBWBT<=!DfU#Uk8~2I2(P;h1*R?jBf;*5|{d-uH1*y_p($$6pvSz0Q z4fuzV{@1;4*}J>B%O6YD-|#&pIr^;$U(;D#UEP|iS6*c|I})`lGGV)Go{WI7M?Hv) z-gqJkf^V?dkfj*d8HKzmZ%QevHyTHc&r((v)hw3aJ-Pe4KR?|rTopEHI9tD zzTGQ*JOX2OiYWRt8F=6&D!Mie-q7T-Ok;PrFS6ugv8omM&X&l#zusC_MT2r&-|Y;* zoe5$kmW~63GeN#5Hx`*D*x|(1Cq&bL#DeB#+!y`TWxzvo4cKAja@TEvqmAFR;R6-e z%5=!a=A721eE92O8lm7qVvA%n613YMI3~3co+fn)c~c#&f|hd0f$dST{Rrt47Q-#o zt%)k5mne}AkicQV5xRU}eJMBciGeninabU8k57ZJp+oE_umYwz=qXR(VIu~M(^M%! z&|r_k$ZDf2)3Iz)iIhX%6$^=HzvEWB!(}(i9mbut4-c>B_lhC%eFWxj1s0YYi!O5A zN%63?h~g#)+9?_4F55o=_xy2lyPg4kjji_#D^52Ri18G|aC|%K(qw{9#X|! zZgepO=UaEm^;ICdv((!zh8fhcEFPa7%H`{oXo#&ECn{C)iGgJW1;tv*Qs^ zyJdJ?y5EM=gV%F+f5aB`0gnzC66BCsbr3A@VTk1m>rt;zSV>h&&CG`5+4vXLn|#r} z23v>TYm?VB^`JjEVF!30&cJD<7gUVoGME1FvDu9HBk`$Dmt1GawJpxhA&C& zAEy`4JaVHhl8WF!XYGg441cdq5uI3aSp=iz${IZA=1&SMXUBQiHHQNo#R$K8w+melyf)#gt zeUI-5<1C>=Z9aws9NJkDD0yZ(T|)vOb&{D(9%E`919co*P_>U^EQ=m>v1*MX9Mxl) z9>G>UTxv!y-}m1Jv-1n5o_c}dc2%JsI!=_a;a_5gortL3IRQf4c=lJxyvRY#?5yr2 zX)@C202b)`9oWDktmg8JaoVvQm(vlNG;@_y;vFEbGCDge8f^!w60gm{9oF}fric;BN*07)tA-UUjS+9 zOVlK5lgU8Sg+;j;fr$95AUQr?!(I?hMxpONdkyv2qd*mQ!r)32nQ^4=n)cN+SEYR? z_7|Cnz9Wl{tN-TMcs&CvbCv+~2y*-y?;Xs}AlW=Ru~w3470^1Y;aAG{I1Y^5fW$tm zuBCYdP*Pr7bEfEMxJ58%(^kUwCnZAfhSPO~ra&G9#MZjSL2o*p%$ktn0~@S3!{+vW zwR1-H<$uoPjnLy|+(58dXDx$C#*M9!WHX1G$$ zKP=CGc6tCD7{B#3^7fm;6_xAE@MV`W!|o~K#Oj3|!AA4d{6l&)^% zwQ+^Rp5@>+=B(m5kQ3PZkN@X(rGjPrAIXcn1q?6)LqA}btJ|=O#v}$@rV@~>jt;e{ zXf6cE`%DO4Grh*F0{S+n&;|S9dUY6lkT=HlZGcBr&8EZP0X-ADpx`eP(hjx?i2_)@e$Q2ve2Ad!>DIIA}eAd|E zZ{De)Qi+rfr^cI=3teLF-YJyJ>xoM=uCVCk()fLR>k3q+5edi0KZ@Ca6Q~0t<*427LGYmo@Zwl>JB~O<7%&FcngkQ3}Lc1U_p!$xVBt^?J^#pRX&y!yK z3s#WZc~8N0sZn(1qxP-NylwZTk|JEI(N9(Ooi&b)eORt@65;@l&Y-oyi)Qem859=- z9T(SD2e3R{Ge~d6^O^w&=zR6!Q2=JJ-H3Ooi@1VBzeE8pshqMlZN+1?54f9+75dUQPA&X-d1bW#ANCmBdY z5oimfv+(+IDyAz~KAu8t)?2*6i)umrdlaw*W3IQ?hB-)!0vMAvq7u=P>prB<%@uPk zQ`9I2I|`p4>(UBYi)bbSY2#}+D~1qF2VhBw<_>fb2!}3#G3>B=Y{?r3$;~g(HE=bc z{4A-%tH6bOE1r-|K&hg{PrD_U-`r5jmEy9dQ3`rgprH_41M_Y-&E8ev{N47-_Cw-5 z1uicxDoRaU4K zmsMKlpmCh2-tLf*K)BL@uLvn9izV|s`}7XMANv(3{So@d(B&>XT#S5()*g#`;#VcA z(HGV4qpy*H=gQgt$jV$$Nx@tgR=d7lcGYxP>qa5Bsh<>O+77doTwd(vcaWcM_7cH+7b_8XI!GeR zTcs>;2E<gg1_7#4T`3y)^50z#ZGT|!nEJubd>Pi+N28ZA5m+Oll>2$F1 zOgfW6BQJM5`b@sc6Gk=zxf_S%mHWM4g5@~@;J(@Agt4ku*V@rw5?Kb`KhQ)Z&sKb{ zQG6MGv&{8?eAAunsG}2nONhj5a097FWd0{i&G?blc(=Y6D}zVv7AHDZVx;h$y1E=sdf|x}Dq7qmGb&G7Pfm?gI2w0cV{8=0xfeFG}4yhne&-V=F1iVpLWqm7#^LK;zU7vk~eCKb00My)PF zmW^MpE@062=ZF}r&XlB38_Wv}eY?O0Phx&HCKcTumYZKzyY0p#K6L4G3sm(!Ux*&} z6?xsp?H)8J-(^7tWC!?*IO(z(t7R_4LoRfa!A0&4WS;Mb?diTz0 z8WMGgtK^D{_n#fXwlJq6&e_Vg{swBY&1R1PYL7PHN||D&u;_$N zhc#_q^uePkUsN26(LU*}gk+A7EQL9yLS7i4b&8bQIdDFc85}N0*&p_UO~b`Vs`Geh zIjXjvLG(F$1juq{ejaQcT&%vSpgZDOh;sGkAosbq7*l`|{%GzrOY*QktS?;RcEm=+BCtaT7>cEHnJ>UnD$l zn!IR*`+#M5G3%eXr~N0*LVxAIzJDv9S5GW?G3(!+w>CSS5xCF&>G9vGD-QSghabAF z-sR-tOa>=JH_-x=x6O~M`bzLPaF|Fw0b)x~m3xFixvt#eX@d{e8q1?bwN-WrQ8^=S z)fo*HbIpMxiMmik02fa37Ex~qJcNi-H?b#t%z)rD#gTmKkLhvyc()tw|Lr6KTpyDE z)qhCJm>*PI;&tDFyF))|LI`+AKt>5eRtLOLs&&$Q?_UhmWgBb3P%qnEOaFdO|)XmzK29)8Kim=^>UEDarT!6f*j)_9v3jGv@ePZBC@2zsWfs zEJ$r?ZBh{0eh-(gnKGOF(S8qqG=(+!W32xiFWgK1Xlko3V@-grX@OS8I{adKxobce zr_&o))@>a5K)^@?Ya=s*K6cx!C3?WX@A{;bL4-C7fW{(J3p7dd%+6x zV#$UJBhA@o{ujHWJ7BwVAepq!gq zd~kTuiPGNRQq40?LH*f)faV1bDFDXhN$yvd8;ddEx@rPAcDEJ-KoTHu0O2Np%(4L`_$8TI8zAAvp+SxpJZP)LOAavL zYd;=Fq^|Tj={qewR@WUbFS`Bt*^rFsQTL*IHkwn#?yTEijwa*Y*~Orx>TXfPB^d-G zvPQ2n0i8E$2x#~tXwSGGuG{?TN6V*hSrz`UP;i0E3W9wH5Mf5O#v=gWf^XA*cb7@g z){{DKQK8TK>cj;At6_RELKcT)R9JV$JMa5g-rta?t-Q3g6>5tygI|6dsP-hEU*i*p zz(Z6_in|krC`|PjSd_Rma>rq37KhCL5QlX9wR#2kM!3Urex;vO^{_{|rbhk40XM*Y zqBzl6ok7w{(G$E1e^u_B4$wY>!8pO-GBUFph7pT!Kv&b9+f<0;da$CqQZV0mUL1BM zZbPtrV_l>P*GvS^Hb2mz=|g7ncZzoa^-hZ}hvL<8PXr$r|C_u2T1r}N&c3bkq_UAOokvaVHXln;Zu-2_8lbrxr~MsP=0** z$CJlG`>Eeqi|!I`48MC=@Gzd3F3Yi;I=u}!{rS@(8ewKHHzOlibhEY7o7>gZ*x;bL zO44zpmsV~wru=>&sP+b2LtQh(Kw;&-zca;s4CZkZ#XSOAz1)l(B?$Upq?dMrN=n4E zH|VBieo9IUzyHn@_c8FuQ55$G{`f+1HN_?La)6-7rtubcYlBf%i7rrB`S0&caUTPA z9Yt}EAe1i@7bz~Kmq!VzwZTgU=1T_Vkr|lS74~PB^d0U!qO-fN4Ez zn%2X?bo7Z#>rWf3JH~GPZbK;rD(I4};EOck=Zqg3EYf%^$!v>=azFs@03h5`9! zuAUhYMYlIAAt{+fEBNqwi;-%%Y-DTH(~Bk`FOZ94kfVk93*xiNh~T7FddvqFg?%(E z@pM3J&ctO$vOp=9HbM`|*8;P&luu%iW44gHiHW?F zda1&^d}fAL=XT?R5VM8o@O`nv@RtX+oRW~Wb&`Q4&$&H}iy9%jAINURuxB~nxq1m- zTRER2W+SC7Li=zy$dM8bo>1#w8W898;$nCAUp0{T|i)npc<=VCoEjY)UepV@NfaTI(1N!W?t?Rm@@Ll z%`9x8&lXPm0IT-sfvu+EX1%y!v{r1A0A@N1A=d!Mr&EelxB)s}zdQNoFW;PWyYa@{ zlf1>@DU66vd91X9VC0C9W{wjpP7?}6{mA)p#k4GEN%yQ%84di)?p;)xEXDo==Vui= zI2^x#@2b+hPsF5&j%w1C4iKrXfm-csbARV0sdI_rK`;3se6cE0wy4dDw*j7kS47!= ze|E(pEi%|VDn-*U#zQNnv|f@R4tWqpR0n>zfnu@ZnM$|)5#UO3fX{NN&qR)%SbEWo za*{l5d~cs4<$c2$4+_MfFBm(~5_}GA2gmSJ)gOcoES+rizm9cVArx1m&DOl7@=4oW z0WQsiKt`1y2bWdGexM)RYCauZq)^vB3w!G52J$Kg)YSa5;2LbX@9!WY#r4f`KU-Df z5vW$6h+K+4vfoC z=&NZJM($@lYmx3nM{DRCo6RxPbzj8QlNYR!DKja4UuCF2WqnNMDOIMG^ zZm|JKHFvq(RyTmE5+%_dBv}fzW7~ygZs#yTw$y?oe3tH-<$_R4_tsYTldaZ~Vmf?> znJG+Bg)nRO|HNZ&wNOae&d>Wf*)CSi&dVHks^KF3Tq~pll|@R=88fknk#cQn@`?6c zx+<+l@C8+=SM4QU0hT0rjH~>ras611opgG&Es+1EkS=Wj3sW7>;=)D%o59~Csxra6 z{THNtEQ=67FsLr^v`v83gV@aVu+a1&Er%?g>5QVs#)Lpq)`CIilw(f=1O#R%8f?w~ zsr=$gTtuuO0x42HLiweY>J8O63i-&Mp=B({l87+tZ1|fkT8wat9XA_Fm7*}b5~yK~ zUSlyf?=EkCO_C%5U|Z98JikJ@(S16kFUatsU`(t{ zvY$w64CQ#jcORytF)3RZK)z(rA#kE4^2@^_=u{8FB4B#`8TYaufK>u2k}X34Y5?4T ztaz)hmZ(AURvx~{F%5$+RKpES#pAONEJ8P}0iVys+X~!vH0yez4YIs#F7H4IGLzqf z0HI7ZFoLu7s81t=5)tdmYhH?=Hg*r{g7RPIRJ>Qj3dNNGyC~A7t78meJj=;h1rhq; z#&OkoQJ{}dPSC-6R2Sr_fKUfVFs`pgqst2Z{R20J8Z?0o%54PXJMI~CV*!o=*l2#s zg;;@}oB>)G!qjYXw>h9@S;JbX>-?bWE`E@%=Km7(-VWcW+GVhw&E?+JdVvinu+oov zwq%_s?y^}Nakxh0Cp`*89W?)-)#)sh8iO&yIk-bdI4)75R8MsaD>YJ|uKGi8qK7w3rcj?bVw$lN&($cO3h#O0~6&c z7!qP-?Sc2e8L9mRpA?V`HSxctrxW<^`R>*zhR&G^<;~`6iePC%>><2t9%jQ-cG$JW z96G-5Fm-S6qFDc|(m_Jaji|N+-MYW29*;P;Sn1juk#_mQQ6(tS4SUfWU*cRpCeAhU zxpjK|aJZiCjiV%4tvCM5EwR7tGE$=mm~ni57PG4_@it`)M4P13!Mz4W^2WNh^t-So2cwQXs(Nf-E6 zq{l05=I~WF{-+vq4brH(sNyrKzu8}_O?=iH@~!$$@(jbJ@oY#r*0LQ}Kb6n8qlz8K z`|EQTY2(?LJ3otdqd^}U&EFjj9yjO0 zMeL3KCPP5n;$3RjHlNw~%f^@uC*@pvU^bItEHB$DNG&%`qlKFz4@%MwrcwXEVGKMZ58USk&?vh1DC+ zMqGE+@ljDYqb& ze#zT;Hf*f(WHfG^!3i0WCB00>s>qVB)nq)xuTVZ4NwLhZG7*HYLAU~ zCk2%WgHjvX&h7P8)nH2Ku+Xz=fk8eC{&Puct^6)n$i`D|43FxruYTDF_@y2Y9f8KPWgo*_XR?{Y_AeX;DrErArlsCaNoE!GDaJsJDE60jqf#F6Y*Ok&!(P8j9AnWR zRej2JLGe_Sjs6p45ARM(xsSr%Rk&E*4)I&k5Xr&K`3=YrF5k5~T8sXq6qfBqC`9Em z_)xhnNEG45+59kj8tqt7B?f2hI3SI85uxQ-Mc`o|74MEvG8@mp{Y$zUvvK7bU5PzNudQ$Db~`^T}j#)*Vm!%h}?5KE9Y=jL*ks7mHKsq&`PnHk2%o)TQUn%LnT_SE-oE<$?H2b}Z^)t7Z|@Y7ENdU6#zGPd0AMKwqu#4O M{Nd04`sUUD0~>|hw*UYD literal 0 HcmV?d00001 From 8cae0ca62ba5dc19e8f829978501db8fba7639c1 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:23:46 +0200 Subject: [PATCH 55/57] Update deps (#4) * Update deps * Update arb bridge --------- Co-authored-by: telome <> --- lib/arbitrum-token-bridge | 2 +- lib/dss-test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index 643c62b..d2b2185 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit 643c62bd11c929cc47d0789b76f17022e21156de +Subproject commit d2b218520fc8b39f3c8661ab03f814d2a8567b41 diff --git a/lib/dss-test b/lib/dss-test index 41066f6..f2a2b2b 160000 --- a/lib/dss-test +++ b/lib/dss-test @@ -1 +1 @@ -Subproject commit 41066f6d18202c61208d8cf09b38532a6f5b0d0a +Subproject commit f2a2b2bbea71921103c5b7cf3cb1d241b957bec7 From b8795494941ae4b0630b22d6b21fa3d16fd8a1d1 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 23 Aug 2024 14:14:35 +0200 Subject: [PATCH 56/57] Update CI --- .env.example | 2 +- .github/workflows/test.yml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml diff --git a/.env.example b/.env.example index 8016f33..b725086 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ export FOUNDRY_SCRIPT_DEPS=deployed export FOUNDRY_EXPORTS_OVERWRITE_LATEST=true export L1="sepolia" export L2="arbitrum_one_sepolia" -export ETH_RPC_URL= +export MAINNET_RPC_URL= export ARBITRUM_ONE_RPC_URL= export SEPOLIA_RPC_URL= export ARBITRUM_ONE_SEPOLIA_RPC_URL= diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fd09b9d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: test + +on: [push, pull_request] + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge build + run: | + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test + env: + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} + ARBITRUM_ONE_RPC_URL: ${{ secrets.ARBITRUM_ONE_RPC_URL }} From 370f3dac0fe13c9fa77fd99f63ce98c1028f7052 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:18:28 -0300 Subject: [PATCH 57/57] Upgrade bridge dependency + minor adjustment (#5) --- lib/arbitrum-token-bridge | 2 +- test/Integration.t.sol | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/arbitrum-token-bridge b/lib/arbitrum-token-bridge index d2b2185..542f2cf 160000 --- a/lib/arbitrum-token-bridge +++ b/lib/arbitrum-token-bridge @@ -1 +1 @@ -Subproject commit d2b218520fc8b39f3c8661ab03f814d2a8567b41 +Subproject commit 542f2cf623cd8c9873c07b52e068346a00600b30 diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 6dfbb27..a85ade6 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -97,8 +97,7 @@ contract IntegrationTest is DssTest { owner: PAUSE_PROXY, l2Gateway: address(l2Gateway), l1Router: L1_ROUTER, - inbox: inbox, - escrow: ESCROW + inbox: inbox }); assertEq(address(l1Gateway), l1Gateway_);