Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ There are currently five ARM contracts:

See the [ARM Registry](https://docs.originprotocol.com/registry/contracts/arm-registry) for the deployed contracts.

## Asset Adapter Interface

The generic ARM integrates protocol-specific asynchronous redemption flows through `IAssetAdapter`. An adapter converts between a base asset's shares and the ARM's liquidity asset, queues redemptions, and transfers claimed liquidity back to the ARM.

The mint functions are part of the same generic interface. Redemption-only adapters revert with `MintNotSupported` when either mint function is called.

The ARM's adapter request and claim operations are implemented in the externally linked `ARMAdapterLib`. The ARM retains its public interface and authorization checks while the linked library performs adapter calls and updates the ARM's explicitly passed storage references.

```Solidity
interface IAssetAdapter {
function asset() external view returns (address);
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function requestRedeem(uint256 shares)
external
returns (uint256 sharesRequested, uint256 assetsExpected);
function redeem(uint256 shares)
external
returns (uint256 sharesClaimed, uint256 assetsExpected, uint256 assetsReceived);
function requestMint(uint256 assets)
external
returns (uint256 assetsRequested, uint256 sharesExpected);
function claimMint(uint256 shares)
external
returns (uint256 sharesClaimed, uint256 assetsExpected, uint256 sharesReceived);
}
```

## Swap Interface

[Uniswap V2 Router](https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02) compatible interface for swapping ERC20 tokens.
Expand Down
72 changes: 72 additions & 0 deletions script/deploy/helpers/ARMDeploymentHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;

import {Vm} from "forge-std/Vm.sol";

import {MultiAssetARM} from "contracts/MultiAssetARM.sol";
import {ARMAdapterLib} from "contracts/libraries/ARMAdapterLib.sol";

/// @title ARM deployment helper
/// @notice Deploys ARMAdapterLib and MultiAssetARM implementations linked to a specific library instance.
/// @dev Keeps unlinked MultiAssetARM creation bytecode out of dynamically loaded deployment scripts.
library ARMDeploymentHelper {
/// @dev Solidity's link placeholder for contracts/libraries/ARMAdapterLib.sol:ARMAdapterLib.
string internal constant ARM_ADAPTER_LIB_PLACEHOLDER = "__$13fc881d3fc475fdd3ac87119115f1ebd2$__";

struct MultiAssetARMConfig {
/// @notice Asset used for LP deposits, redeem claims, and base-asset quote pricing.
address liquidityAsset;
/// @notice Delay before an LP redeem request can be claimed. For example, 600 = 10 minutes.
uint256 claimDelay;
/// @notice Minimum market shares redeemed during allocation. For example, 1e6 = 1 share for a 6-decimal market.
uint256 minSharesToRedeem;
/// @notice Minimum excess liquidity moved during allocation. For example, 100e6 = 100 units for a 6-decimal asset.
int256 allocateThreshold;
}

function deployARMAdapterLib() internal returns (address adapterLib) {
bytes memory creationCode = type(ARMAdapterLib).creationCode;
assembly ("memory-safe") {
adapterLib := create(0, add(creationCode, 0x20), mload(creationCode))
}
require(adapterLib != address(0), "ARMAdapterLib deployment failed");
}

function deployMultiAssetARM(
Vm vm,
string memory projectRoot,
address adapterLib,
MultiAssetARMConfig memory config
) internal returns (MultiAssetARM armImpl) {
string memory artifactPath = string.concat(projectRoot, "/out/MultiAssetARM.sol/MultiAssetARM.json");
string memory artifact = vm.readFile(artifactPath);
string memory creationCodeHex = vm.parseJsonString(artifact, ".bytecode.object");
string memory linkedCreationCodeHex =
vm.replace(creationCodeHex, ARM_ADAPTER_LIB_PLACEHOLDER, _addressWithoutHexPrefix(vm, adapterLib));
require(
keccak256(bytes(linkedCreationCodeHex)) != keccak256(bytes(creationCodeHex)),
"ARMAdapterLib placeholder not found"
);

bytes memory creationCode = abi.encodePacked(
vm.parseBytes(linkedCreationCodeHex),
abi.encode(config.liquidityAsset, config.claimDelay, config.minSharesToRedeem, config.allocateThreshold)
);

address implementation;
assembly ("memory-safe") {
implementation := create(0, add(creationCode, 0x20), mload(creationCode))
}
require(implementation != address(0), "MultiAssetARM deployment failed");
armImpl = MultiAssetARM(payable(implementation));
}

function _addressWithoutHexPrefix(Vm vm, address account) private pure returns (string memory) {
bytes memory prefixed = bytes(vm.toString(account));
bytes memory unprefixed = new bytes(40);
for (uint256 i = 0; i < 40; ++i) {
unprefixed[i] = prefixed[i + 2];
}
return string(unprefixed);
}
}
14 changes: 8 additions & 6 deletions script/deploy/mainnet/000_Example.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ pragma solidity ^0.8.36;

// Contracts to deploy/upgrade
import {Proxy} from "contracts/Proxy.sol";
import {LidoARM} from "contracts/LidoARM.sol";
import {CapManager} from "contracts/CapManager.sol";
import {ZapperLidoARM} from "contracts/ZapperLidoARM.sol";

Expand Down Expand Up @@ -63,7 +62,7 @@ contract $000_Example is AbstractDeployScript("000_Example") {

// Declare variables here for contracts deployed in _execute()
// that need to be referenced in _buildGovernanceProposal() or _fork()
LidoARM public newImplementation;
address public newImplementation;

// ==================== Main Deployment Logic ==================== //

Expand Down Expand Up @@ -92,8 +91,11 @@ contract $000_Example is AbstractDeployScript("000_Example") {
// Deploy your contracts here. The deployer address is already set via
// vm.broadcast (real) or vm.prank (fork).

// Example: Deploy a new implementation contract
newImplementation = new LidoARM(weth, 10 minutes, 0, 0);
// Example: Deploy a new implementation contract. Keep concrete deployment imports and code
// in the copied script so this permanently skipped template remains safe to load even when
// an implementation requires external library linking.
// MyImplementation implementation = new MyImplementation(constructorArgs);
// newImplementation = address(implementation);

// Example: Deploy a proxy with implementation
// Proxy proxy = new Proxy();
Expand All @@ -105,7 +107,7 @@ contract $000_Example is AbstractDeployScript("000_Example") {
// - Available to subsequent scripts via resolver.resolve()
// - Logged for visibility

_recordDeployment("LIDO_ARM_IMPL", address(newImplementation));
_recordDeployment("LIDO_ARM_IMPL", newImplementation);

// Note: You can register multiple contracts
// _recordDeployment("MY_PROXY", address(proxy));
Expand Down Expand Up @@ -140,7 +142,7 @@ contract $000_Example is AbstractDeployScript("000_Example") {

// Example 1: Upgrade a proxy to new implementation
address lidoArmProxy = resolver.resolve("LIDO_ARM");
govProposal.action(lidoArmProxy, "upgradeTo(address)", abi.encode(address(newImplementation)));
govProposal.action(lidoArmProxy, "upgradeTo(address)", abi.encode(newImplementation));

// Example 2: Set a configuration value
// address capManager = resolver.resolve("CAP_MANAGER");
Expand Down
62 changes: 38 additions & 24 deletions script/deploy/mainnet/024_UpgradeOETHARMDepositScript.s.sol
Original file line number Diff line number Diff line change
@@ -1,35 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;

// Contract
import {OriginARM} from "contracts/OriginARM.sol";
import {Mainnet} from "contracts/utils/Addresses.sol";
// The original contract and governance imports are retained as comments with the implementation
// below. This script was superseded before execution and is permanently skipped. DeployManager must
// still deploy the script artifact to read skip(), so active OriginARM creation code would introduce
// an unresolved ARMAdapterLib link and prevent the deployment runner from loading this skip marker.
// import {OriginARM} from "contracts/OriginARM.sol";
// import {Mainnet} from "contracts/utils/Addresses.sol";
// import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol";

// Deployment
import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol";
import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol";

/// @dev Legacy deployment retained as an inert skip marker. It was superseded before execution.
contract $024_UpgradeOETHARMDepositScript is AbstractDeployScript("024_UpgradeOETHARMDepositScript") {
using GovHelper for GovProposal;

bool public constant override skip = true;

function _execute() internal override {
// 1. Deploy new OriginARM implementation
uint256 claimDelay = 10 minutes;
uint256 minSharesToRedeem = 1e7;
int256 allocateThreshold = 1e18;
OriginARM originARMImpl = new OriginARM(
Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT, claimDelay, minSharesToRedeem, allocateThreshold
);
_recordDeployment("OETH_ARM_IMPL", address(originARMImpl));
}

function _buildGovernanceProposal() internal override {
govProposal.setDescription("Upgrade OETH ARM to restrict deposits during insolvency");

govProposal.action(
resolver.resolve("OETH_ARM"), "upgradeTo(address)", abi.encode(resolver.resolve("OETH_ARM_IMPL"))
);
}
// The original deployment logic is commented out for the same linking reason documented above.
// It remains here as historical context for why this deployment file exists.
//
// using GovHelper for GovProposal;
//
// function _execute() internal override {
// // 1. Deploy new OriginARM implementation
// uint256 claimDelay = 10 minutes;
// uint256 minSharesToRedeem = 1e7;
// int256 allocateThreshold = 1e18;
// OriginARM originARMImpl = new OriginARM(
// Mainnet.OETH,
// Mainnet.WETH,
// Mainnet.OETH_VAULT,
// claimDelay,
// minSharesToRedeem,
// allocateThreshold
// );
// _recordDeployment("OETH_ARM_IMPL", address(originARMImpl));
// }
//
// function _buildGovernanceProposal() internal override {
// govProposal.setDescription("Upgrade OETH ARM to restrict deposits during insolvency");
//
// govProposal.action(
// resolver.resolve("OETH_ARM"),
// "upgradeTo(address)",
// abi.encode(resolver.resolve("OETH_ARM_IMPL"))
// );
// }
}
53 changes: 31 additions & 22 deletions script/deploy/mainnet/025_UpgradeLidoARMDepositScript.s.sol
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;

// Contract
import {LidoARM} from "contracts/LidoARM.sol";
import {Mainnet} from "contracts/utils/Addresses.sol";
// The original contract and governance imports are retained as comments with the implementation
// below. This script was superseded before execution and is permanently skipped. DeployManager must
// still deploy the script artifact to read skip(), so active LidoARM creation code would introduce
// an unresolved ARMAdapterLib link and prevent the deployment runner from loading this skip marker.
// import {LidoARM} from "contracts/LidoARM.sol";
// import {Mainnet} from "contracts/utils/Addresses.sol";
// import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol";

// Deployment
import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol";
import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol";

/// @dev Legacy deployment retained as an inert skip marker. It was superseded before execution.
contract $025_UpgradeLidoARMDepositScript is AbstractDeployScript("025_UpgradeLidoARMDepositScript") {
using GovHelper for GovProposal;

bool public constant override skip = true;

function _execute() internal override {
// 1. Deploy new LidoARM implementation
uint256 claimDelay = 10 minutes;
uint256 minSharesToRedeem = 1e7;
int256 allocateThreshold = 1e18;
LidoARM lidoARMImpl = new LidoARM(Mainnet.WETH, claimDelay, minSharesToRedeem, allocateThreshold);
_recordDeployment("LIDO_ARM_IMPL", address(lidoARMImpl));
}

function _buildGovernanceProposal() internal override {
govProposal.setDescription("Upgrade Lido ARM to restrict deposits during insolvency");

govProposal.action(
resolver.resolve("LIDO_ARM"), "upgradeTo(address)", abi.encode(resolver.resolve("LIDO_ARM_IMPL"))
);
}
// The original deployment logic is commented out for the same linking reason documented above.
// It remains here as historical context for why this deployment file exists.
//
// using GovHelper for GovProposal;
//
// function _execute() internal override {
// // 1. Deploy new LidoARM implementation
// uint256 claimDelay = 10 minutes;
// uint256 minSharesToRedeem = 1e7;
// int256 allocateThreshold = 1e18;
// LidoARM lidoARMImpl = new LidoARM(Mainnet.WETH, claimDelay, minSharesToRedeem, allocateThreshold);
// _recordDeployment("LIDO_ARM_IMPL", address(lidoARMImpl));
// }
//
// function _buildGovernanceProposal() internal override {
// govProposal.setDescription("Upgrade Lido ARM to restrict deposits during insolvency");
//
// govProposal.action(
// resolver.resolve("LIDO_ARM"),
// "upgradeTo(address)",
// abi.encode(resolver.resolve("LIDO_ARM_IMPL"))
// );
// }
}
Loading
Loading