diff --git a/contracts/contracts/proxies/OBTCProxies.sol b/contracts/contracts/proxies/OBTCProxies.sol new file mode 100644 index 0000000000..82c8b4fd87 --- /dev/null +++ b/contracts/contracts/proxies/OBTCProxies.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { InitializeGovernedUpgradeabilityProxy } from "./InitializeGovernedUpgradeabilityProxy.sol"; + +/** + * @notice OBTCProxy delegates calls to an OBTC implementation + */ +contract OBTCProxy is InitializeGovernedUpgradeabilityProxy { + +} + +/** + * @notice WrappedOBTCProxy delegates calls to a WrappedOBTC implementation + */ +contract WOBTCProxy is InitializeGovernedUpgradeabilityProxy { + +} + +/** + * @notice OBTCVaultProxy delegates calls to a Vault implementation + */ +contract OBTCVaultProxy is InitializeGovernedUpgradeabilityProxy { + +} diff --git a/contracts/contracts/token/OBTC.sol b/contracts/contracts/token/OBTC.sol new file mode 100644 index 0000000000..55b7c69844 --- /dev/null +++ b/contracts/contracts/token/OBTC.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { OUSD } from "./OUSD.sol"; + +/** + * @title OETH Token Contract + * @author Origin Protocol Inc + */ +contract OBTC is OUSD { + function symbol() external pure override returns (string memory) { + return "OBTC"; + } + + function name() external pure override returns (string memory) { + return "Origin Bitcoin"; + } + + function decimals() external pure override returns (uint8) { + return 18; + } +} diff --git a/contracts/contracts/token/WOBTC.sol b/contracts/contracts/token/WOBTC.sol new file mode 100644 index 0000000000..8aebd90e26 --- /dev/null +++ b/contracts/contracts/token/WOBTC.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { WOETH } from "./WOETH.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @title Wrapped OBTC Token Contract + * @author Origin Protocol Inc + */ + +contract WOBTC is WOETH { + constructor(ERC20 underlying_) WOETH(underlying_) {} + + function name() public view virtual override returns (string memory) { + return "Wrapped OBTC"; + } + + function symbol() public view virtual override returns (string memory) { + return "wOBTC"; + } +} diff --git a/contracts/contracts/vault/OBTCVaultAdmin.sol b/contracts/contracts/vault/OBTCVaultAdmin.sol new file mode 100644 index 0000000000..049fa308ce --- /dev/null +++ b/contracts/contracts/vault/OBTCVaultAdmin.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { OETHVaultAdmin } from "./OETHVaultAdmin.sol"; + +/** + * @title OBTC VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OBTCVaultAdmin is OETHVaultAdmin { + constructor(address _wbtc) OETHVaultAdmin(_wbtc) {} +} diff --git a/contracts/contracts/vault/OBTCVaultCore.sol b/contracts/contracts/vault/OBTCVaultCore.sol new file mode 100644 index 0000000000..b6f397aa7d --- /dev/null +++ b/contracts/contracts/vault/OBTCVaultCore.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { StableMath } from "../utils/StableMath.sol"; +import { OETHVaultCore } from "./OETHVaultCore.sol"; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IStrategy } from "../interfaces/IStrategy.sol"; + +/** + * @title OETH Base VaultCore Contract + * @author Origin Protocol Inc + */ +contract OBTCVaultCore is OETHVaultCore { + using SafeERC20 for IERC20; + using StableMath for uint256; + + constructor(address _wbtc) OETHVaultCore(_wbtc) {} + + // @inheritdoc OETHVaultCore + function _redeem(uint256 _amount, uint256 _minimumUnitAmount) + internal + virtual + override + { + // Only Strategist or Governor can redeem using the Vault for now. + // We don't have the onlyGovernorOrStrategist modifier on VaultCore. + // Since we won't be using that modifier anywhere in the VaultCore as well, + // the check has been added inline instead of moving it to VaultStorage. + require( + msg.sender == strategistAddr || isGovernor(), + "Caller is not the Strategist or Governor" + ); + + super._redeem(_amount, _minimumUnitAmount); + } +} diff --git a/contracts/deploy/mainnet/137_obtc.js b/contracts/deploy/mainnet/137_obtc.js new file mode 100644 index 0000000000..8f4926f8cc --- /dev/null +++ b/contracts/deploy/mainnet/137_obtc.js @@ -0,0 +1,178 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const { parseUnits } = require("ethers/lib/utils"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "137_obtc", + forceDeploy: false, + // forceSkip: true, + deployerIsProposer: false, + // proposalId: "", + }, + async ({ deployWithConfirmation, withConfirmation }) => { + const { deployerAddr, strategistAddr, timelockAddr } = + await getNamedAccounts(); + + const sDeployer = await ethers.getSigner(deployerAddr); + + // Deploy Oracle Router + await deployWithConfirmation("OETHFixedOracle", []); + const cOracleRouter = await ethers.getContract("OETHFixedOracle"); + + // Deploy proxies + await deployWithConfirmation("OBTCProxy"); + const cOBTCProxy = await ethers.getContract("OBTCProxy"); + await deployWithConfirmation("WOBTCProxy"); + const cWOBTCProxy = await ethers.getContract("WOBTCProxy"); + await deployWithConfirmation("OBTCVaultProxy"); + const cOBTCVaultProxy = await ethers.getContract("OBTCVaultProxy"); + + // Temp Dripper + const dTempDripper = await deployWithConfirmation("FixedRateDripper", [ + cOBTCVaultProxy.address, + addresses.mainnet.WBTC, + ]); + console.log("FixedRateDripper deployed at", dTempDripper.address); + + // Deploy core contracts + const dOBTC = await deployWithConfirmation("OBTC"); + const dwOBTC = await deployWithConfirmation("WOBTC", [cOBTCProxy.address]); + const dOBTCVaultCore = await deployWithConfirmation("OBTCVaultCore", [ + addresses.mainnet.WBTC, + ]); + const cOBTCVaultCore = await ethers.getContract("OBTCVaultCore"); + const dOBTCVaultAdmin = await deployWithConfirmation("OBTCVaultAdmin", [ + addresses.mainnet.WBTC, + ]); + + // Get contract instances + const cOBTC = await ethers.getContractAt("OBTC", cOBTCProxy.address); + const cwOBTC = await ethers.getContractAt("WOBTC", cWOBTCProxy.address); + const cOBTCVault = await ethers.getContractAt( + "IVault", + cOBTCVaultProxy.address + ); + + // Init OBTC + const resolution = ethers.utils.parseUnits("1", 27); + const initDataOBTC = cOBTC.interface.encodeFunctionData( + "initialize(address,uint256)", + [cOBTCVaultProxy.address, resolution] + ); + // prettier-ignore + await withConfirmation( + cOBTCProxy + .connect(sDeployer)["initialize(address,address,bytes)"]( + dOBTC.address, + timelockAddr, + initDataOBTC + ) + ); + console.log("Initialized OBTCProxy and OBTC implementation"); + + // Init OBTCVault + const initDataOBTCVault = cOBTCVaultCore.interface.encodeFunctionData( + "initialize(address,address)", + [cOracleRouter.address, cOBTCProxy.address] + ); + // prettier-ignore + await withConfirmation( + cOBTCVaultProxy + .connect(sDeployer)["initialize(address,address,bytes)"]( + dOBTCVaultCore.address, + timelockAddr, + initDataOBTCVault + ) + ); + console.log("Initialized OBTCVault"); + + // Init wOBTC + const initDatawOBTC = cwOBTC.interface.encodeFunctionData( + "initialize()", + [] + ); + // prettier-ignore + await withConfirmation( + cWOBTCProxy + .connect(sDeployer)["initialize(address,address,bytes)"]( + dwOBTC.address, + timelockAddr, + initDatawOBTC + ) + ); + console.log("Initialized wOBTC"); + + return { + name: "Deploy OBTC", + actions: [ + { + // Set VaultCore implementation + contract: cOBTCVaultProxy, + signature: "upgradeTo(address)", + args: [dOBTCVaultCore.address], + }, + { + // Set VaultAdmin implementation + contract: cOBTCVault, + signature: "setAdminImpl(address)", + args: [dOBTCVaultAdmin.address], + }, + { + // Allow minting with WBTC + contract: cOBTCVault, + signature: "supportAsset(address,uint8)", + args: [addresses.mainnet.WBTC, 0], + }, + { + // Unpause Capital + contract: cOBTCVault, + signature: "unpauseCapital()", + args: [], + }, + { + // Set async claim delay + contract: cOBTCVault, + signature: "setWithdrawalClaimDelay(uint256)", + args: [24 * 60 * 60], + }, + { + // Set rebase threshold + contract: cOBTCVault, + signature: "setRebaseThreshold(uint256)", + args: [parseUnits("0.01", 18)], // 0.01 wBTC + }, + { + // Set strategist + contract: cOBTCVault, + signature: "setStrategistAddr(address)", + args: [strategistAddr], + }, + { + // Set max supply diff + contract: cOBTCVault, + signature: "setMaxSupplyDiff(uint256)", + args: [parseUnits("1", 18)], // 1 wBTC + }, + { + // Set dripper + contract: cOBTCVault, + signature: "setDripper(address)", + args: [dTempDripper.address], + }, + { + // Set drip duration + contract: cOBTCVault, + signature: "setDripDuration(uint256)", + args: [7 * 24 * 60 * 60], // 1 week + }, + { + // Set max rebase rate + contract: cOBTCVault, + signature: "setRebaseRateMax(uint256)", + args: [parseUnits("10", 18)], // 10% + }, + ], + }; + } +); diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index a3199e3698..587a4f566a 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -329,6 +329,9 @@ addresses.mainnet.passthrough.uniswap.OETH_OGN = addresses.mainnet.passthrough.uniswap.OETH_WETH = "0x216dEBBF25e5e67e6f5B2AD59c856Fc364478A6A"; +// OBTC +addresses.mainnet.WBTC = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"; + // Arbitrum One addresses.arbitrumOne = {}; addresses.arbitrumOne.WOETHProxy = "0xD8724322f44E5c58D7A815F542036fb17DbbF839";