Skip to content

Commit b26695a

Browse files
authored
Base Hydrex AMO (#2886)
* initial commit of the hydrex AMO * move gauge mock to deploy file * move gauge mock to deploy file * remove mock gauge * adjust harvester
1 parent bc044c5 commit b26695a

11 files changed

Lines changed: 567 additions & 7 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/**
5+
* @title IHydrexGauge
6+
* @notice Minimal interface exposing the staked-token getter used by the
7+
* Hydrex GaugeV2 (>= v2.5). Hydrex renamed `TOKEN()` to `stakeToken()`
8+
* in v2.5; the rest of the gauge surface (deposit / withdraw /
9+
* getReward / emergency / emergencyWithdraw / balanceOf) is
10+
* ABI-compatible with `IAlgebraGauge` and is invoked through that
11+
* interface elsewhere in the strategy.
12+
*/
13+
interface IHydrexGauge {
14+
function stakeToken() external view returns (address);
15+
}

contracts/contracts/proxies/Proxies.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,10 @@ contract OUSDMorphoV2StrategyProxy is InitializeGovernedUpgradeabilityProxy {
249249
contract OETHSupernovaAMOProxy is InitializeGovernedUpgradeabilityProxy {
250250

251251
}
252+
253+
/**
254+
* @notice OETHbHydrexAMOProxy delegates calls to an OETHbHydrexAMOStrategy implementation
255+
*/
256+
contract OETHbHydrexAMOProxy is InitializeGovernedUpgradeabilityProxy {
257+
258+
}

contracts/contracts/strategies/algebra/OETHSupernovaAMOStrategy.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pragma solidity ^0.8.0;
77
* @author Origin Protocol Inc
88
*/
99
import { StableSwapAMMStrategy } from "./StableSwapAMMStrategy.sol";
10+
import { IGauge } from "../../interfaces/algebra/IAlgebraGauge.sol";
1011

1112
contract OETHSupernovaAMOStrategy is StableSwapAMMStrategy {
1213
/**
@@ -15,6 +16,6 @@ contract OETHSupernovaAMOStrategy is StableSwapAMMStrategy {
1516
* @param _gauge Address of the Supernova gauge for the pool.
1617
*/
1718
constructor(BaseStrategyConfig memory _baseConfig, address _gauge)
18-
StableSwapAMMStrategy(_baseConfig, _gauge)
19+
StableSwapAMMStrategy(_baseConfig, _gauge, IGauge(_gauge).TOKEN())
1920
{}
2021
}

contracts/contracts/strategies/algebra/StableSwapAMMStrategy.sol

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,18 @@ contract StableSwapAMMStrategy is InitializableAbstractStrategy {
159159
* @param _baseConfig The `platformAddress` is the address of the Algebra pool.
160160
* The `vaultAddress` is the address of the Origin Vault.
161161
* @param _gauge Address of the Algebra gauge for the pool.
162+
* @param _gaugeStakeToken The pool LP token address as reported by the
163+
* gauge. The inheriting contract is expected to resolve this via
164+
* whichever getter its gauge exposes (e.g. `IGauge.TOKEN()` for
165+
* legacy GaugeV2 ≤ v2.4 or `IHydrexGauge.stakeToken()` for
166+
* Hydrex GaugeV2 ≥ v2.5) and pass the result here. The constructor
167+
* verifies it matches `_baseConfig.platformAddress`.
162168
*/
163-
constructor(BaseStrategyConfig memory _baseConfig, address _gauge)
164-
InitializableAbstractStrategy(_baseConfig)
165-
{
169+
constructor(
170+
BaseStrategyConfig memory _baseConfig,
171+
address _gauge,
172+
address _gaugeStakeToken
173+
) InitializableAbstractStrategy(_baseConfig) {
166174
// Read the oToken address from the Vault
167175
address oTokenMem = IVault(_baseConfig.vaultAddress).oToken();
168176
address assetMem = IVault(_baseConfig.vaultAddress).asset();
@@ -178,9 +186,11 @@ contract StableSwapAMMStrategy is InitializableAbstractStrategy {
178186
IPair(_baseConfig.platformAddress).isStable() == true,
179187
"Pool not stable"
180188
);
181-
// Check the gauge is for the pool
189+
// Check the gauge is wired to the expected pool LP token. The
190+
// inheriting contract is responsible for fetching `_gaugeStakeToken`
191+
// from whichever getter the underlying gauge variant exposes.
182192
require(
183-
IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,
193+
_gaugeStakeToken == _baseConfig.platformAddress,
184194
"Incorrect gauge"
185195
);
186196
oTokenPoolIndex = IPair(_baseConfig.platformAddress).token0() ==
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.0;
3+
4+
/**
5+
* @title OETHb Hydrex Algorithmic Market Maker (AMO) Strategy
6+
* @notice AMO strategy for the Hydrex superOETHb/WETH stable pool on Base
7+
* @author Origin Protocol Inc
8+
*/
9+
import { StableSwapAMMStrategy } from "../algebra/StableSwapAMMStrategy.sol";
10+
import { IHydrexGauge } from "../../interfaces/hydrex/IHydrexGauge.sol";
11+
12+
contract OETHbHydrexAMOStrategy is StableSwapAMMStrategy {
13+
/**
14+
* @param _baseConfig The `platformAddress` is the address of the Hydrex superOETHb/WETH pool.
15+
* The `vaultAddress` is the address of the OETHBase Vault.
16+
* @param _gauge Address of the Hydrex gauge for the pool. Hydrex GaugeV2
17+
* (>= v2.5) renamed `TOKEN()` to `stakeToken()`, which is what we
18+
* resolve here and forward to the parent.
19+
*/
20+
constructor(BaseStrategyConfig memory _baseConfig, address _gauge)
21+
StableSwapAMMStrategy(
22+
_baseConfig,
23+
_gauge,
24+
IHydrexGauge(_gauge).stakeToken()
25+
)
26+
{}
27+
}

contracts/contracts/strategies/sonic/SonicSwapXAMOStrategy.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pragma solidity ^0.8.0;
77
* @author Origin Protocol Inc
88
*/
99
import { StableSwapAMMStrategy } from "../algebra/StableSwapAMMStrategy.sol";
10+
import { IGauge } from "../../interfaces/algebra/IAlgebraGauge.sol";
1011

1112
contract SonicSwapXAMOStrategy is StableSwapAMMStrategy {
1213
/**
@@ -15,6 +16,6 @@ contract SonicSwapXAMOStrategy is StableSwapAMMStrategy {
1516
* @param _gauge Address of the SwapX gauge for the pool.
1617
*/
1718
constructor(BaseStrategyConfig memory _baseConfig, address _gauge)
18-
StableSwapAMMStrategy(_baseConfig, _gauge)
19+
StableSwapAMMStrategy(_baseConfig, _gauge, IGauge(_gauge).TOKEN())
1920
{}
2021
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const { deployOnBase } = require("../../utils/deploy-l2");
2+
const addresses = require("../../utils/addresses");
3+
const {
4+
deployOETHbHydrexAMOStrategyImplementation,
5+
} = require("../deployActions");
6+
7+
module.exports = deployOnBase(
8+
{
9+
deployName: "048_oethb_hydrex_amo",
10+
},
11+
async ({ deployWithConfirmation, ethers }) => {
12+
// 1. Deploy the OETHb Hydrex AMO proxy
13+
await deployWithConfirmation("OETHbHydrexAMOProxy");
14+
const cOETHbHydrexAMOProxy = await ethers.getContract(
15+
"OETHbHydrexAMOProxy"
16+
);
17+
18+
// 2. Deploy & initialize the strategy implementation against the live
19+
// Hydrex gauge configured in addresses.base.HydrexOETHb_WETH.gauge.
20+
const cOETHbHydrexAMOStrategy =
21+
await deployOETHbHydrexAMOStrategyImplementation(
22+
addresses.base.HydrexOETHb_WETH.gauge
23+
);
24+
25+
// 3. Connect to the OETHBase Vault as IVault
26+
const cOETHBaseVaultProxy = await ethers.getContract("OETHBaseVaultProxy");
27+
const cVault = await ethers.getContractAt(
28+
"IVault",
29+
cOETHBaseVaultProxy.address
30+
);
31+
32+
// 4. Connect to the OETHBase harvester proxy. Use the same harvester
33+
// that AerodromeAMOStrategy uses (OETHHarvesterSimple via the
34+
// OETHBaseHarvesterProxy) so reward token flows go through the
35+
// standard Origin harvester pipeline.
36+
const cHarvesterProxy = await ethers.getContract("OETHBaseHarvesterProxy");
37+
const cHarvester = await ethers.getContractAt(
38+
"OETHHarvesterSimple",
39+
cHarvesterProxy.address
40+
);
41+
42+
return {
43+
name: "Deploy OETHb Hydrex AMO Strategy on Base",
44+
actions: [
45+
// Approve the strategy on the OETHBase Vault
46+
{
47+
contract: cVault,
48+
signature: "approveStrategy(address)",
49+
args: [cOETHbHydrexAMOProxy.address],
50+
},
51+
// Allow the strategy to mint OETHb via the Vault
52+
{
53+
contract: cVault,
54+
signature: "addStrategyToMintWhitelist(address)",
55+
args: [cOETHbHydrexAMOProxy.address],
56+
},
57+
// Set the harvester address on the strategy. Rewards (oHYDX) flow
58+
// strategy → harvester → strategist via OETHHarvesterSimple's
59+
// harvestAndTransfer.
60+
{
61+
contract: cOETHbHydrexAMOStrategy,
62+
signature: "setHarvesterAddress(address)",
63+
args: [cHarvesterProxy.address],
64+
},
65+
// Mark the strategy as supported on the harvester so
66+
// harvestAndTransfer(strategy) doesn't revert.
67+
{
68+
contract: cHarvester,
69+
signature: "setSupportedStrategy(address,bool)",
70+
args: [cOETHbHydrexAMOProxy.address, true],
71+
},
72+
],
73+
};
74+
}
75+
);

contracts/deploy/deployActions.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,55 @@ const deployOETHSupernovaAMOStrategyImplementation = async () => {
899899
return cOETHSupernovaAMOStrategy;
900900
};
901901

902+
const deployOETHbHydrexAMOStrategyImplementation = async (gaugeAddress) => {
903+
const { deployerAddr } = await getNamedAccounts();
904+
const sDeployer = await ethers.provider.getSigner(deployerAddr);
905+
906+
// Default to the addresses entry so any other caller still works; the
907+
// 048_oethb_hydrex_amo deploy script always passes the live Hydrex gauge
908+
// address explicitly.
909+
const _gauge = gaugeAddress || addresses.base.HydrexOETHb_WETH.gauge;
910+
911+
const cOETHbHydrexAMOStrategyProxy = await ethers.getContract(
912+
"OETHbHydrexAMOProxy"
913+
);
914+
const cOETHBaseVaultProxy = await ethers.getContract("OETHBaseVaultProxy");
915+
916+
// Deploy OETHb Hydrex AMO Strategy implementation
917+
const dHydrexAMOStrategy = await deployWithConfirmation(
918+
"OETHbHydrexAMOStrategy",
919+
[
920+
[addresses.base.HydrexOETHb_WETH.pool, cOETHBaseVaultProxy.address],
921+
_gauge,
922+
]
923+
);
924+
925+
const cOETHbHydrexAMOStrategy = await ethers.getContractAt(
926+
"OETHbHydrexAMOStrategy",
927+
cOETHbHydrexAMOStrategyProxy.address
928+
);
929+
930+
// Initialize OETHb Hydrex AMO Strategy via the proxy.
931+
// Reward token is oHYDX (call option on HYDX). The Hydrex gauge emits oHYDX
932+
// from getReward(); off-chain plumbing exercises/sells it.
933+
const depositPriceRange = parseUnits("0.01", 18); // 1% or 100 basis points
934+
const initData = cOETHbHydrexAMOStrategy.interface.encodeFunctionData(
935+
"initialize(address[],uint256)",
936+
[[addresses.base.oHYDX], depositPriceRange]
937+
);
938+
await withConfirmation(
939+
// prettier-ignore
940+
cOETHbHydrexAMOStrategyProxy
941+
.connect(sDeployer)["initialize(address,address,bytes)"](
942+
dHydrexAMOStrategy.address,
943+
addresses.base.timelock,
944+
initData
945+
)
946+
);
947+
948+
return cOETHbHydrexAMOStrategy;
949+
};
950+
902951
const getCreate2ProxiesFilePath = async () => {
903952
const networkName =
904953
isFork || isForkTest || isCI ? "localhost" : await getNetworkName();
@@ -1270,6 +1319,7 @@ module.exports = {
12701319
deploySonicSwapXAMOStrategyImplementation,
12711320
deploySonicSwapXAMOStrategyImplementationAndInitialize,
12721321
deployOETHSupernovaAMOStrategyImplementation,
1322+
deployOETHbHydrexAMOStrategyImplementation,
12731323
deployProxyWithCreateX,
12741324
deployCrossChainMasterStrategyImpl,
12751325
deployCrossChainRemoteStrategyImpl,

0 commit comments

Comments
 (0)