From 0f927edd23e2c14e3cf59fe7828328bd92954563 Mon Sep 17 00:00:00 2001 From: Lienid <0xLienid@protonmail.com> Date: Fri, 14 Oct 2022 11:25:05 -0400 Subject: [PATCH 1/2] V2 --- contracts/interfaces/IBondSDA.sol | 7 +- contracts/interfaces/IBondTeller.sol | 2 +- contracts/interfaces/IEasyAuction.sol | 6 +- contracts/peripheral/OhmBondManager.sol | 45 +++++----- test/bonds/OhmBondManager.test.ts | 104 +++++++++++++++++++++++- 5 files changed, 139 insertions(+), 25 deletions(-) diff --git a/contracts/interfaces/IBondSDA.sol b/contracts/interfaces/IBondSDA.sol index 653636f52..fc27248df 100644 --- a/contracts/interfaces/IBondSDA.sol +++ b/contracts/interfaces/IBondSDA.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.15; +pragma solidity 0.7.5; import {IERC20} from "./IERC20.sol"; @@ -8,4 +8,9 @@ interface IBondSDA { /// @param params_ Configuration data needed for market creation /// @return id ID of new bond market function createMarket(bytes calldata params_) external returns (uint256); + + /// @notice Disable existing bond market + /// @notice Must be market owner + /// @param id_ ID of market to close + function closeMarket(uint256 id_) external; } diff --git a/contracts/interfaces/IBondTeller.sol b/contracts/interfaces/IBondTeller.sol index f6f15aacd..3eb1587b4 100644 --- a/contracts/interfaces/IBondTeller.sol +++ b/contracts/interfaces/IBondTeller.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.15; +pragma solidity 0.7.5; import {IERC20} from "./IERC20.sol"; diff --git a/contracts/interfaces/IEasyAuction.sol b/contracts/interfaces/IEasyAuction.sol index d857e786b..f2f28ca65 100644 --- a/contracts/interfaces/IEasyAuction.sol +++ b/contracts/interfaces/IEasyAuction.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.15; +pragma solidity 0.7.5; import {IERC20} from "./IERC20.sol"; @@ -29,4 +29,8 @@ interface IEasyAuction { address accessManager, bytes calldata accessManagerData ) external returns (uint256); + + /// @notice Settles an auction and identifies the clearing price + /// @param auctionId The ID of the auction to settle + function settleAuction(uint256 auctionId) external returns (bytes32 clearingOrder); } diff --git a/contracts/peripheral/OhmBondManager.sol b/contracts/peripheral/OhmBondManager.sol index e8d6f6284..89648e9bb 100644 --- a/contracts/peripheral/OhmBondManager.sol +++ b/contracts/peripheral/OhmBondManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity 0.8.15; +pragma solidity 0.7.5; import {IBondSDA} from "../interfaces/IBondSDA.sol"; import {IBondTeller} from "../interfaces/IBondTeller.sol"; @@ -8,6 +8,7 @@ import {IERC20} from "../interfaces/IERC20.sol"; import {ITreasury} from "../interfaces/ITreasury.sol"; import {IOlympusAuthority} from "../interfaces/IOlympusAuthority.sol"; import {OlympusAccessControlled} from "../types/OlympusAccessControlled.sol"; +import "../types/ERC20Permit.sol"; contract OhmBondManager is OlympusAccessControlled { // ========= DATA STRUCTURES ========= // @@ -30,7 +31,7 @@ contract OhmBondManager is OlympusAccessControlled { // ========= STATE VARIABLES ========= // /// Tokens - IERC20 public ohm; + ERC20Permit public ohm; /// Contract Dependencies ITreasury public treasury; @@ -52,7 +53,7 @@ contract OhmBondManager is OlympusAccessControlled { address gnosisAuction_, address authority_ ) OlympusAccessControlled(IOlympusAuthority(authority_)) { - ohm = IERC20(ohm_); + ohm = ERC20Permit(ohm_); treasury = ITreasury(treasury_); fixedExpiryAuctioneer = IBondSDA(feAuctioneer_); fixedExpiryTeller = IBondTeller(feTeller_); @@ -61,9 +62,9 @@ contract OhmBondManager is OlympusAccessControlled { // ========= MARKET CREATION ========= // function createBondProtocolMarket(uint256 capacity_, uint256 bondTerm_) external onlyPolicy returns (uint256) { - _topUpOhm(capacity_); + treasury.mint(address(this), capacity_); - /// Encodes the information needed for creating a bond market on Bond Protocol + // Encodes the information needed for creating a bond market on Bond Protocol bytes memory createMarketParams = abi.encode( ohm, // payoutToken ohm, // quoteToken @@ -79,23 +80,27 @@ contract OhmBondManager is OlympusAccessControlled { int8(0) // scaleAdjustment ); - ohm.approve(address(fixedExpiryTeller), capacity_); + ohm.increaseAllowance(address(fixedExpiryTeller), capacity_); uint256 marketId = fixedExpiryAuctioneer.createMarket(createMarketParams); return marketId; } + function closeBondProtocolMarket(uint256 id_) external onlyPolicy { + fixedExpiryAuctioneer.closeMarket(id_); + } + function createGnosisAuction(uint96 capacity_, uint256 bondTerm_) external onlyPolicy returns (uint256) { - _topUpOhm(capacity_); + treasury.mint(address(this), capacity_); uint48 expiry = uint48(block.timestamp + bondTerm_); - /// Create bond token - ohm.approve(address(fixedExpiryTeller), capacity_); + // Create bond token + ohm.increaseAllowance(address(fixedExpiryTeller), capacity_); fixedExpiryTeller.deploy(ohm, expiry); (IERC20 bondToken, ) = fixedExpiryTeller.create(ohm, expiry, capacity_); - /// Launch Gnosis Auction + // Launch Gnosis Auction bondToken.approve(address(gnosisEasyAuction), capacity_); uint256 auctionId = gnosisEasyAuction.initiateAuction( bondToken, // auctioningToken @@ -114,6 +119,10 @@ contract OhmBondManager is OlympusAccessControlled { return auctionId; } + function settleGnosisAuction(uint256 id_) external onlyPolicy { + gnosisEasyAuction.settleAuction(id_); + } + // ========= PARAMETER ADJUSTMENT ========= // function setBondProtocolParameters( uint256 initialPrice_, @@ -147,18 +156,12 @@ contract OhmBondManager is OlympusAccessControlled { }); } - // ========= INTERNAL FUNCTIONS ========= // - function _topUpOhm(uint256 amountToDeploy_) internal { - uint256 ohmBalance = ohm.balanceOf(address(this)); - - if (amountToDeploy_ > ohmBalance) { - uint256 amountToMint = amountToDeploy_ - ohmBalance; - treasury.mint(address(this), amountToMint); - } + // ========= EMERGENCY FUNCTIONS ========= // + function setEmergencyApproval(address token_, address spender_, uint256 amount_) external onlyPolicy { + IERC20(token_).approve(spender_, amount_); } - // ========= EMERGENCY FUNCTIONS ========= // - function emergencyWithdraw(uint256 amount) external onlyPolicy { - ohm.transfer(address(treasury), amount); + function emergencyWithdraw(uint256 amount_) external onlyPolicy { + ohm.transfer(address(treasury), amount_); } } diff --git a/test/bonds/OhmBondManager.test.ts b/test/bonds/OhmBondManager.test.ts index 465d0cde5..995a372a8 100644 --- a/test/bonds/OhmBondManager.test.ts +++ b/test/bonds/OhmBondManager.test.ts @@ -9,11 +9,11 @@ import { OlympusTreasury, } from "../../types"; import { addEth, addressZero, bne, getCoin, impersonate, pinBlock } from "../utils/scripts"; +import { advanceTime } from "../utils/Utilities"; import { olympus } from "../utils/olympus"; import { coins } from "../utils/coins"; import { BigNumber } from "ethers"; import { easyAuctionAbi, feAuctioneerAbi, feTellerAbi, bondAggregatorAbi } from "../utils/abi"; -import { defaultAbiCoder } from "ethers/lib/utils"; // Network const url: string = config.networks.hardhat.forking!.url; @@ -252,6 +252,37 @@ describe.only("OhmBondManager", () => { }); }); + describe("closeBondProtocolMarket", () => { + beforeEach(async () => { + await ohmBondManager.connect(policy).setBondProtocolParameters( + "1000000000000000000000000000000000000", // 1e36 + "500000000000000000000000000000000000", // 5e35 + 100_000, + 604800, + 21600 + ); + await ohmBondManager + .connect(policy) + .createBondProtocolMarket("10000000000000", 1210000); + }); + + it("can only be called by policy", async () => { + const marketId = (await bondAggregator.marketCounter()).sub("1"); + await expect(ohmBondManager.connect(policy).closeBondProtocolMarket(marketId)).to.not.be.reverted; + + await expect(ohmBondManager.connect(other).closeBondProtocolMarket(marketId)).to.be.reverted; + + await expect(ohmBondManager.connect(guardian).closeBondProtocolMarket(marketId)).to.be.reverted; + }); + + it("should correctly close the market", async () => { + const marketId = (await bondAggregator.marketCounter()).sub("1"); + await ohmBondManager.connect(policy).closeBondProtocolMarket(marketId); + + expect((await feAuctioneer.isLive(marketId))).to.be.false; + }); + }); + describe("createGnosisAuction", () => { beforeEach(async () => { await ohmBondManager @@ -290,6 +321,77 @@ describe.only("OhmBondManager", () => { }); }); + describe("settleGnosisAuction", () => { + beforeEach(async () => { + await ohmBondManager + .connect(policy) + .setGnosisAuctionParameters(518400, 604800, 2, "10000000", "1000000000000"); + + await ohmBondManager.connect(policy).createGnosisAuction("10000000000000", 1210000); + }); + + it("can only be called by policy", async () => { + await advanceTime(1210005); + + const auctionId = await easyAuction.auctionCounter(); + await expect(ohmBondManager.connect(policy).settleGnosisAuction(auctionId)).to.not.be.reverted; + + await expect(ohmBondManager.connect(other).settleGnosisAuction(auctionId)).to.be.reverted; + + await expect(ohmBondManager.connect(guardian).settleGnosisAuction(auctionId)).to.be.reverted; + }); + + it("should settle the auction", async () => { + await advanceTime(1210005); + + const auctionId = await easyAuction.auctionCounter(); + await expect(ohmBondManager.connect(policy).settleGnosisAuction(auctionId)).to.not.be.reverted; + + const auctionData = await easyAuction.auctionData(auctionId); + expect(auctionData.minimumBiddingAmountPerOrder).to.equal("0"); + }); + }); + + describe("series of market creations", () => { + beforeEach(async () => { + await ohmBondManager.connect(policy).setBondProtocolParameters( + "1000000000000000000000000000000000000", // 1e36 + "500000000000000000000000000000000000", // 5e35 + 100_000, + 604800, + 21600 + ); + + await ohmBondManager + .connect(policy) + .setGnosisAuctionParameters(518400, 604800, 2, "10000000", "1000000000000"); + }); + + it("should not steal OHM when launching subsequent markets", async () => { + await ohmBondManager.connect(policy).createBondProtocolMarket(10000000000000, 1210000); + await ohmBondManager.connect(policy).createGnosisAuction(10000000000000, 1210000); + + expect((await ohm.allowance(ohmBondManager.address, feTeller.address))).to.equal(10000000000000); + expect((await ohm.balanceOf(ohmBondManager.address))).to.equal(10000000000000); + }); + }); + + describe("setEmergencyApproval", () => { + it("can only be called by policy", async () => { + await expect(ohmBondManager.connect(policy).setEmergencyApproval(ohm.address, feTeller.address, "10000000000000")).to.not.be.reverted; + + await expect(ohmBondManager.connect(other).setEmergencyApproval(ohm.address, feTeller.address, "10000000000000")).to.be.reverted; + + await expect(ohmBondManager.connect(guardian).setEmergencyApproval(ohm.address, feTeller.address, "10000000000000")).to.be.reverted; + }); + + it("should set approval on the passed token", async () => { + expect((await ohm.allowance(ohmBondManager.address, feTeller.address))).to.equal("0"); + await ohmBondManager.connect(policy).setEmergencyApproval(ohm.address, feTeller.address, "10000000000000"); + expect((await ohm.allowance(ohmBondManager.address, feTeller.address))).to.equal("10000000000000"); + }); + }); + describe("emergencyWithdraw", () => { beforeEach(async () => { ohm.connect(guardian).transfer(ohmBondManager.address, "1000000000000"); From c6f6a5d171144a34ce2cc218fa2941c8fef1edd9 Mon Sep 17 00:00:00 2001 From: Lienid <0xLienid@protonmail.com> Date: Fri, 14 Oct 2022 11:58:44 -0400 Subject: [PATCH 2/2] Update version, new IERC20 interface --- contracts/interfaces/IBondSDA.sol | 4 ++-- contracts/interfaces/IBondTeller.sol | 4 ++-- contracts/interfaces/IERC20.v2.sol | 28 +++++++++++++++++++++++++ contracts/interfaces/IEasyAuction.sol | 4 ++-- contracts/peripheral/OhmBondManager.sol | 9 ++++---- 5 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 contracts/interfaces/IERC20.v2.sol diff --git a/contracts/interfaces/IBondSDA.sol b/contracts/interfaces/IBondSDA.sol index fc27248df..0abf45c44 100644 --- a/contracts/interfaces/IBondSDA.sol +++ b/contracts/interfaces/IBondSDA.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.7.5; +pragma solidity 0.8.15; -import {IERC20} from "./IERC20.sol"; +import {IERC20} from "./IERC20.v2.sol"; interface IBondSDA { /// @notice Creates a new bond market diff --git a/contracts/interfaces/IBondTeller.sol b/contracts/interfaces/IBondTeller.sol index 3eb1587b4..9c748b6b4 100644 --- a/contracts/interfaces/IBondTeller.sol +++ b/contracts/interfaces/IBondTeller.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.7.5; +pragma solidity 0.8.15; -import {IERC20} from "./IERC20.sol"; +import {IERC20} from "./IERC20.v2.sol"; interface IBondTeller { /// @notice Instantiates a new fixed expiry bond token diff --git a/contracts/interfaces/IERC20.v2.sol b/contracts/interfaces/IERC20.v2.sol new file mode 100644 index 000000000..953687d1c --- /dev/null +++ b/contracts/interfaces/IERC20.v2.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.15; + +interface IERC20 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address recipient, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/contracts/interfaces/IEasyAuction.sol b/contracts/interfaces/IEasyAuction.sol index f2f28ca65..cec1285ba 100644 --- a/contracts/interfaces/IEasyAuction.sol +++ b/contracts/interfaces/IEasyAuction.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.7.5; +pragma solidity 0.8.15; -import {IERC20} from "./IERC20.sol"; +import {IERC20} from "./IERC20.v2.sol"; interface IEasyAuction { /// @notice Initiates an auction through Gnosis Auctions diff --git a/contracts/peripheral/OhmBondManager.sol b/contracts/peripheral/OhmBondManager.sol index 89648e9bb..154234db2 100644 --- a/contracts/peripheral/OhmBondManager.sol +++ b/contracts/peripheral/OhmBondManager.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity 0.7.5; +pragma solidity 0.8.15; import {IBondSDA} from "../interfaces/IBondSDA.sol"; import {IBondTeller} from "../interfaces/IBondTeller.sol"; import {IEasyAuction} from "../interfaces/IEasyAuction.sol"; -import {IERC20} from "../interfaces/IERC20.sol"; +import {IERC20} from "../interfaces/IERC20.v2.sol"; import {ITreasury} from "../interfaces/ITreasury.sol"; import {IOlympusAuthority} from "../interfaces/IOlympusAuthority.sol"; import {OlympusAccessControlled} from "../types/OlympusAccessControlled.sol"; -import "../types/ERC20Permit.sol"; contract OhmBondManager is OlympusAccessControlled { // ========= DATA STRUCTURES ========= // @@ -31,7 +30,7 @@ contract OhmBondManager is OlympusAccessControlled { // ========= STATE VARIABLES ========= // /// Tokens - ERC20Permit public ohm; + IERC20 public ohm; /// Contract Dependencies ITreasury public treasury; @@ -53,7 +52,7 @@ contract OhmBondManager is OlympusAccessControlled { address gnosisAuction_, address authority_ ) OlympusAccessControlled(IOlympusAuthority(authority_)) { - ohm = ERC20Permit(ohm_); + ohm = IERC20(ohm_); treasury = ITreasury(treasury_); fixedExpiryAuctioneer = IBondSDA(feAuctioneer_); fixedExpiryTeller = IBondTeller(feTeller_);