From 804e550c79deffbd7dd491b08dd1ca1cf3dd98a4 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Sat, 24 Aug 2024 12:42:59 +0200 Subject: [PATCH 1/6] Add MakerEscrow --- src/escrows/MakerEscrow.sol | 119 ++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/escrows/MakerEscrow.sol diff --git a/src/escrows/MakerEscrow.sol b/src/escrows/MakerEscrow.sol new file mode 100644 index 00000000..96bf4766 --- /dev/null +++ b/src/escrows/MakerEscrow.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +/// * @dev Caution: We assume all failed transfers cause reverts and ignore the returned bool. +interface IERC20 { + function transfer(address,uint) external returns (bool); + function transferFrom(address,address,uint) external returns (bool); + function balanceOf(address) external view returns (uint); +} + +interface IVoteDelegate { + function lock(uint) external; + function free(uint) external; + function stake(address) external returns(uint); +} + +interface IVoteDelegateFactory { + function isDelegate(address) external returns(bool); + function delegate(address) external returns(address); +} + +/** + * @title Simple ERC20 Escrow + * @notice Collateral is stored in unique escrow contracts for every user and every market. + * @dev Caution: This is a proxy implementation. Follow proxy pattern best practices + */ +contract MakerEscrow { + address public market; + address public delegate; + address public beneficiary; + IVoteDelegateFactory public constant voteDelegateFactory = IVoteDelegateFactory(0xD897F108670903D1d6070fcf818f9db3615AF272); + IERC20 public token; + + error OnlyBeneficiary(); + error AddressNotDelegate(); + + /** + * @notice Initialize escrow with a token + * @dev Must be called right after proxy is created + * @param _token The IERC20 token to be stored in this specific escrow + * @param _beneficiary Address of the owner of the escrow + */ + function initialize(IERC20 _token, address _beneficiary) public { + require(market == address(0), "ALREADY INITIALIZED"); + market = msg.sender; + token = _token; + beneficiary = _beneficiary; + } + + /** + * @notice Transfers the associated ERC20 token to a recipient. + * @param recipient The address to receive payment from the escrow + * @param amount The amount of ERC20 token to be transferred. + */ + function pay(address recipient, uint amount) public { + require(msg.sender == market, "ONLY MARKET"); + token.transfer(recipient, amount); + } + + /** + * @notice Get the token balance of the escrow + * @return Uint representing the token balance of the escrow + */ + function balance() public view returns (uint) { + return token.balanceOf(address(this)); + } + + /** + * @notice Function called by market on deposit. Function is empty for this escrow. + * @dev This function should remain callable by anyone to handle direct inbound transfers. + */ + function onDeposit() external { + uint mkrBal = token.balanceOf(address(this)); + if(delegate != address(0)){ + //TODO: Check if this will fail if some mkr has already been locked/delegated + IVoteDelegate voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + uint staked = voteDelegate.stake(address(this)); + voteDelegate.lock(mkrBal - staked); + } else if(voteDelegateFactory.isDelegate(beneficiary)){ + delegate = beneficiary; + IVoteDelegate voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(beneficiary)); + uint staked = voteDelegate.stake(address(this)); + voteDelegate.lock(mkrBal - staked); + } + } + + /** + * @notice Delegates all voting power to the address `_newDelegate` + * @dev `_newDelegate` is not the address of a `VoteDelegate` contract but the owner of such a contract. + * @param _newDelegate Address of the new delegate to delegate to + */ + function delegateTo(address _newDelegate) external { + if(msg.sender != beneficiary) revert OnlyBeneficiary(); + if(!voteDelegateFactory.isDelegate(_newDelegate)) revert AddressNotDelegate(); + IVoteDelegate voteDelegate; + if(delegate != address(0)){ + voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + uint stake = voteDelegate.stake(address(this)); + voteDelegate.free(stake); + } + delegate = _newDelegate; + uint mkrBal = token.balanceOf(address(this)); + voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + voteDelegate.lock(mkrBal); + } + + /** + * @notice Undelegates from the current delegate + */ + function undelegate() external { + if(msg.sender != beneficiary) revert OnlyBeneficiary(); + if(delegate != address(0)){ + IVoteDelegate voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + uint stake = voteDelegate.stake(address(this)); + voteDelegate.free(stake); + delegate = address(0); + } + } +} From 91281e04cf02ab5cf0c2a4741ae294683f2138d5 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Mon, 26 Aug 2024 11:55:01 +0200 Subject: [PATCH 2/6] Add escrow test, fix errors --- src/escrows/MakerEscrow.sol | 73 ++++++++++------- test/escrowForkTests/MakerEscrowFork.t.sol | 94 ++++++++++++++++++++++ 2 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 test/escrowForkTests/MakerEscrowFork.t.sol diff --git a/src/escrows/MakerEscrow.sol b/src/escrows/MakerEscrow.sol index 96bf4766..c2b8d10a 100644 --- a/src/escrows/MakerEscrow.sol +++ b/src/escrows/MakerEscrow.sol @@ -1,22 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -/// * @dev Caution: We assume all failed transfers cause reverts and ignore the returned bool. -interface IERC20 { - function transfer(address,uint) external returns (bool); - function transferFrom(address,address,uint) external returns (bool); - function balanceOf(address) external view returns (uint); -} +import "src/interfaces/IERC20.sol"; +/// * @dev Caution: We assume all failed transfers cause reverts and ignore the returned bool. interface IVoteDelegate { function lock(uint) external; function free(uint) external; function stake(address) external returns(uint); + function delegate() external returns(address); } interface IVoteDelegateFactory { function isDelegate(address) external returns(bool); - function delegate(address) external returns(address); + function delegates(address) external returns(address); } /** @@ -26,24 +23,23 @@ interface IVoteDelegateFactory { */ contract MakerEscrow { address public market; - address public delegate; address public beneficiary; + IVoteDelegate public voteDelegate; IVoteDelegateFactory public constant voteDelegateFactory = IVoteDelegateFactory(0xD897F108670903D1d6070fcf818f9db3615AF272); - IERC20 public token; + address public constant chief = 0x0a3f6849f78076aefaDf113F5BED87720274dDC0; + IERC20 public constant iou = IERC20(0xA618E54de493ec29432EbD2CA7f14eFbF6Ac17F7); + IERC20 public constant token = IERC20(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); error OnlyBeneficiary(); - error AddressNotDelegate(); /** * @notice Initialize escrow with a token * @dev Must be called right after proxy is created - * @param _token The IERC20 token to be stored in this specific escrow * @param _beneficiary Address of the owner of the escrow */ - function initialize(IERC20 _token, address _beneficiary) public { + function initialize(IERC20, address _beneficiary) public { require(market == address(0), "ALREADY INITIALIZED"); market = msg.sender; - token = _token; beneficiary = _beneficiary; } @@ -54,6 +50,10 @@ contract MakerEscrow { */ function pay(address recipient, uint amount) public { require(msg.sender == market, "ONLY MARKET"); + uint mkrBal = token.balanceOf(address(this)); + if(amount > mkrBal){ + voteDelegate.free(amount - mkrBal); + } token.transfer(recipient, amount); } @@ -62,7 +62,7 @@ contract MakerEscrow { * @return Uint representing the token balance of the escrow */ function balance() public view returns (uint) { - return token.balanceOf(address(this)); + return token.balanceOf(address(this)) + iou.balanceOf(address(this)); } /** @@ -71,16 +71,14 @@ contract MakerEscrow { */ function onDeposit() external { uint mkrBal = token.balanceOf(address(this)); - if(delegate != address(0)){ - //TODO: Check if this will fail if some mkr has already been locked/delegated - IVoteDelegate voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + if(address(voteDelegate) != address(0)){ uint staked = voteDelegate.stake(address(this)); voteDelegate.lock(mkrBal - staked); } else if(voteDelegateFactory.isDelegate(beneficiary)){ - delegate = beneficiary; - IVoteDelegate voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(beneficiary)); - uint staked = voteDelegate.stake(address(this)); - voteDelegate.lock(mkrBal - staked); + _setVoteDelegate( + IVoteDelegate(voteDelegateFactory.delegates(beneficiary)) + ); + voteDelegate.lock(mkrBal); } } @@ -91,29 +89,44 @@ contract MakerEscrow { */ function delegateTo(address _newDelegate) external { if(msg.sender != beneficiary) revert OnlyBeneficiary(); - if(!voteDelegateFactory.isDelegate(_newDelegate)) revert AddressNotDelegate(); - IVoteDelegate voteDelegate; - if(delegate != address(0)){ - voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + if(address(voteDelegate) != address(0)){ uint stake = voteDelegate.stake(address(this)); + iou.approve(address(voteDelegate), stake); voteDelegate.free(stake); } - delegate = _newDelegate; uint mkrBal = token.balanceOf(address(this)); - voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + _setVoteDelegate( + IVoteDelegate(voteDelegateFactory.delegates(_newDelegate)) + ); voteDelegate.lock(mkrBal); } + function _setVoteDelegate(IVoteDelegate newVoteDelegate) internal { + voteDelegate = newVoteDelegate; + iou.approve(address(newVoteDelegate), type(uint).max); + token.approve(address(newVoteDelegate), type(uint).max); + } + /** * @notice Undelegates from the current delegate */ function undelegate() external { if(msg.sender != beneficiary) revert OnlyBeneficiary(); - if(delegate != address(0)){ - IVoteDelegate voteDelegate = IVoteDelegate(voteDelegateFactory.delegate(delegate)); + if(address(voteDelegate) != address(0)){ uint stake = voteDelegate.stake(address(this)); voteDelegate.free(stake); - delegate = address(0); + voteDelegate = IVoteDelegate(address(0)); + } + } + + /** + * @notice Get the owner of the `voteDelegate` contract that is being delegated to. + */ + function delegate() external returns(address){ + if(address(voteDelegate) != address(0)){ + return voteDelegate.delegate(); + } else { + return address(0); } } } diff --git a/test/escrowForkTests/MakerEscrowFork.t.sol b/test/escrowForkTests/MakerEscrowFork.t.sol new file mode 100644 index 00000000..269df65c --- /dev/null +++ b/test/escrowForkTests/MakerEscrowFork.t.sol @@ -0,0 +1,94 @@ +pragma solidity ^0.8.20; + +import "test/escrowForkTests/BaseEscrowTest.t.sol"; +import {MakerEscrow, IVoteDelegate, IVoteDelegateFactory} from "src/escrows/MakerEscrow.sol"; + +contract MakerEscrowFork is BaseEscrowTest { + + IERC20 maker = IERC20(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); + MakerEscrow escrowImplementation; + IVoteDelegateFactory public constant voteDelegateFactory = IVoteDelegateFactory(0xD897F108670903D1d6070fcf818f9db3615AF272); + address delegate = 0xE5a7023f78c3c0b7B098e8f4aCE7031B3D9aFBaB; + + function setUp() public { + string memory url = vm.rpcUrl("mainnet"); + vm.createSelectFork(url); + + escrowImplementation = new MakerEscrow(); + initialize(address(escrowImplementation), address(maker)); + } + + function test_delegateTo() public { + deal(address(maker), address(escrowImplementation), 1 ether); + assertEq(escrowImplementation.delegate(), address(0)); + vm.prank(beneficiary); + escrowImplementation.delegateTo(delegate); + assertEq(escrowImplementation.delegate(), delegate); + assertEq(escrowImplementation.iou().balanceOf(address(escrowImplementation)), 1 ether); + assertEq(escrowImplementation.balance(), 1 ether); + } + + function test_pay_payAfterDelegateTo() public { + deal(address(maker), address(escrowImplementation), 1 ether); + assertEq(escrowImplementation.delegate(), address(0)); + vm.prank(beneficiary); + escrowImplementation.delegateTo(delegate); + vm.roll(block.number + 1); //Will revert if chief is interacted with twice by same address in same block + assertEq(escrowImplementation.delegate(), delegate); + vm.prank(market); + escrowImplementation.pay(beneficiary, 1 ether); + assertEq(maker.balanceOf(beneficiary), 1 ether); + assertEq(escrowImplementation.balance(), 0); + } + + function test_delegateTo_afterPayHalf() public { + deal(address(maker), address(escrowImplementation), 1 ether); + vm.prank(market); + escrowImplementation.pay(beneficiary, 1 ether / 2); + assertEq(maker.balanceOf(beneficiary), 1 ether / 2); + assertEq(escrowImplementation.balance(), 1 ether / 2); + + assertEq(escrowImplementation.delegate(), address(0)); + vm.prank(beneficiary); + escrowImplementation.delegateTo(delegate); + assertEq(escrowImplementation.delegate(), delegate); + assertEq(escrowImplementation.iou().balanceOf(address(escrowImplementation)), 1 ether / 2); + assertEq(escrowImplementation.balance(), 1 ether / 2); + } + + function test_onDepositDelegateCorrectly() public { + MakerEscrow freshEscrow = new MakerEscrow(); + deal(address(maker), address(freshEscrow), 1 ether); + vm.prank(market); + freshEscrow.initialize(maker, delegate); + assertTrue(voteDelegateFactory.isDelegate(freshEscrow.beneficiary()), "`delegate` is not a Delegate"); + uint balBefore = freshEscrow.balance(); + freshEscrow.onDeposit(); + assertEq(freshEscrow.market(), market); + assertEq(freshEscrow.beneficiary(), delegate); + assertEq(address(freshEscrow.token()), address(maker)); + assertEq(freshEscrow.delegate(), freshEscrow.beneficiary(), "Delegate not set"); + assertEq(balBefore, freshEscrow.balance(), "Balance after"); + } + + function test_undelegate() public { + deal(address(maker), address(escrowImplementation), 1 ether); + assertEq(escrowImplementation.delegate(), address(0)); + vm.prank(beneficiary); + escrowImplementation.delegateTo(delegate); + assertEq(escrowImplementation.delegate(), delegate); + + vm.roll(block.number + 1); //Will revert if chief is interacted with twice by same address in same block + vm.prank(beneficiary); + escrowImplementation.undelegate(); + assertEq(escrowImplementation.delegate(), address(0)); + assertEq(maker.balanceOf(address(escrowImplementation)), 1 ether); + } + + function test_delegate_failsWhenCalledByNonBeneficiary() public { + vm.expectRevert(); + vm.prank(holder); + escrowImplementation.delegateTo(holder); + } + +} From 1d8a7e5e11a04ef652d526532898595a01b28cac Mon Sep 17 00:00:00 2001 From: 08xmt Date: Mon, 26 Aug 2024 12:34:51 +0200 Subject: [PATCH 3/6] Add Market fork test --- .../marketForkTests/MakerMarketForkTest.t.sol | 21 +++++++++++++++++++ test/marketForkTests/MarketBaseForkTest.sol | 1 - test/marketForkTests/MarketForkTest.sol | 3 --- 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 test/marketForkTests/MakerMarketForkTest.t.sol diff --git a/test/marketForkTests/MakerMarketForkTest.t.sol b/test/marketForkTests/MakerMarketForkTest.t.sol new file mode 100644 index 00000000..098b805b --- /dev/null +++ b/test/marketForkTests/MakerMarketForkTest.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "./MarketBaseForkTest.sol"; +import "src/escrows/MakerEscrow.sol"; + +contract MakerMarketForkTest is MarketBaseForkTest { + + function setUp() public { + //This will fail if there's no mainnet variable in foundry.toml + string memory url = vm.rpcUrl("mainnet"); + vm.createSelectFork(url); + //For non-deployed markets, instantiate market and feed after fork and use new contract addresses + MakerEscrow escrow = new MakerEscrow(); + IERC20 maker = IERC20(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); + Market market = new Market(gov, lender, pauseGuardian, address(escrow), IDolaBorrowingRights(address(dbr)), maker, IOracle(address(oracle)), 7500, 5000, 1000, true); + address feedAddr = 0xec1D1B3b0443256cc3860e24a46F108e699484Aa ; + _advancedInit(address(market), feedAddr, false); + } +} diff --git a/test/marketForkTests/MarketBaseForkTest.sol b/test/marketForkTests/MarketBaseForkTest.sol index 8dce24eb..112937b1 100644 --- a/test/marketForkTests/MarketBaseForkTest.sol +++ b/test/marketForkTests/MarketBaseForkTest.sol @@ -17,7 +17,6 @@ abstract contract MarketBaseForkTest is MarketForkTest { "Only pause guardian or governance can pause"; address lender = 0x2b34548b865ad66A2B046cb82e59eE43F75B90fd; bool approximateBalance; - BorrowContract borrowContract; function _baseInit(address _market, address _feed) public { diff --git a/test/marketForkTests/MarketForkTest.sol b/test/marketForkTests/MarketForkTest.sol index 60aa7966..b1368c68 100644 --- a/test/marketForkTests/MarketForkTest.sol +++ b/test/marketForkTests/MarketForkTest.sol @@ -24,9 +24,6 @@ contract MarketForkTest is Test, ConfigAddr { address user2 = address(0x70); address replenisher = address(0x71); address collatHolder = address(0xD292b72e5C787f9F7E092aB7802aDDF76930981F); - // address gov = address(0x926dF14a23BE491164dCF93f4c468A50ef659D5B); - //address chair = address(0x8F97cCA30Dbe80e7a8B462F1dD1a51C32accDfC8); - //address pauseGuardian = address(0xE3eD95e130ad9E15643f5A5f232a3daE980784cd); //ERC-20s IMintable DOLA; From 1795f4a799428b9937cac3987a0322ae9f9cad11 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Tue, 27 Aug 2024 14:04:48 +0200 Subject: [PATCH 4/6] Fix review feedback --- src/escrows/MakerEscrow.sol | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/escrows/MakerEscrow.sol b/src/escrows/MakerEscrow.sol index c2b8d10a..913132b2 100644 --- a/src/escrows/MakerEscrow.sol +++ b/src/escrows/MakerEscrow.sol @@ -7,13 +7,14 @@ import "src/interfaces/IERC20.sol"; interface IVoteDelegate { function lock(uint) external; function free(uint) external; - function stake(address) external returns(uint); - function delegate() external returns(address); + function stake(address) external view returns(uint); + function delegate() external view returns(address); + function expiration() external view returns(uint); } interface IVoteDelegateFactory { - function isDelegate(address) external returns(bool); - function delegates(address) external returns(address); + function isDelegate(address) external view returns(bool); + function delegates(address) external view returns(address); } /** @@ -26,7 +27,6 @@ contract MakerEscrow { address public beneficiary; IVoteDelegate public voteDelegate; IVoteDelegateFactory public constant voteDelegateFactory = IVoteDelegateFactory(0xD897F108670903D1d6070fcf818f9db3615AF272); - address public constant chief = 0x0a3f6849f78076aefaDf113F5BED87720274dDC0; IERC20 public constant iou = IERC20(0xA618E54de493ec29432EbD2CA7f14eFbF6Ac17F7); IERC20 public constant token = IERC20(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); @@ -70,11 +70,11 @@ contract MakerEscrow { * @dev This function should remain callable by anyone to handle direct inbound transfers. */ function onDeposit() external { - uint mkrBal = token.balanceOf(address(this)); if(address(voteDelegate) != address(0)){ - uint staked = voteDelegate.stake(address(this)); - voteDelegate.lock(mkrBal - staked); + uint mkrBal = token.balanceOf(address(this)); + voteDelegate.lock(mkrBal); } else if(voteDelegateFactory.isDelegate(beneficiary)){ + uint mkrBal = token.balanceOf(address(this)); _setVoteDelegate( IVoteDelegate(voteDelegateFactory.delegates(beneficiary)) ); @@ -91,7 +91,6 @@ contract MakerEscrow { if(msg.sender != beneficiary) revert OnlyBeneficiary(); if(address(voteDelegate) != address(0)){ uint stake = voteDelegate.stake(address(this)); - iou.approve(address(voteDelegate), stake); voteDelegate.free(stake); } uint mkrBal = token.balanceOf(address(this)); @@ -122,11 +121,23 @@ contract MakerEscrow { /** * @notice Get the owner of the `voteDelegate` contract that is being delegated to. */ - function delegate() external returns(address){ + function delegate() external view returns(address){ if(address(voteDelegate) != address(0)){ return voteDelegate.delegate(); } else { return address(0); } } + + /** + * @notice Return the expiry of the delegation contract, at which point the contract will have to be redelegated. + */ + function expiration() external view returns(uint){ + if(address(voteDelegate) != address(0)){ + return voteDelegate.expiration(); + } else { + return 0; + } + } + } From 508e5b40f35c252ef0ede80a6dce3c7adacfb4de Mon Sep 17 00:00:00 2001 From: 08xmt Date: Tue, 27 Aug 2024 14:25:17 +0200 Subject: [PATCH 5/6] Add expiration test --- test/escrowForkTests/MakerEscrowFork.t.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/escrowForkTests/MakerEscrowFork.t.sol b/test/escrowForkTests/MakerEscrowFork.t.sol index 269df65c..3548df4d 100644 --- a/test/escrowForkTests/MakerEscrowFork.t.sol +++ b/test/escrowForkTests/MakerEscrowFork.t.sol @@ -23,6 +23,7 @@ contract MakerEscrowFork is BaseEscrowTest { assertEq(escrowImplementation.delegate(), address(0)); vm.prank(beneficiary); escrowImplementation.delegateTo(delegate); + assertEq(address(escrowImplementation.voteDelegate()), voteDelegateFactory.delegates(delegate)); assertEq(escrowImplementation.delegate(), delegate); assertEq(escrowImplementation.iou().balanceOf(address(escrowImplementation)), 1 ether); assertEq(escrowImplementation.balance(), 1 ether); @@ -85,10 +86,18 @@ contract MakerEscrowFork is BaseEscrowTest { assertEq(maker.balanceOf(address(escrowImplementation)), 1 ether); } - function test_delegate_failsWhenCalledByNonBeneficiary() public { + function test_delegateTo_failsWhenCalledByNonBeneficiary() public { vm.expectRevert(); vm.prank(holder); escrowImplementation.delegateTo(holder); } + function test_expiration() public { + assertEq(escrowImplementation.expiration(), 0); + vm.prank(beneficiary); + escrowImplementation.delegateTo(delegate); + IVoteDelegate voteDelegate = escrowImplementation.voteDelegate(); + assertEq(escrowImplementation.expiration(), voteDelegate.expiration()); + } + } From 352e6a6fb4d037f4e856cfe297323eda404f7867 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Tue, 27 Aug 2024 15:31:41 +0200 Subject: [PATCH 6/6] Remove whitespace --- test/escrowForkTests/MakerEscrowFork.t.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/escrowForkTests/MakerEscrowFork.t.sol b/test/escrowForkTests/MakerEscrowFork.t.sol index 3548df4d..89740f5c 100644 --- a/test/escrowForkTests/MakerEscrowFork.t.sol +++ b/test/escrowForkTests/MakerEscrowFork.t.sol @@ -72,6 +72,21 @@ contract MakerEscrowFork is BaseEscrowTest { assertEq(balBefore, freshEscrow.balance(), "Balance after"); } + function test_onDeposit_DelegateCorrectlyAfterExpiry() public { + MakerEscrow freshEscrow = new MakerEscrow(); + deal(address(maker), address(freshEscrow), 1 ether); + vm.prank(market); + freshEscrow.initialize(maker, delegate); + assertTrue(voteDelegateFactory.isDelegate(freshEscrow.beneficiary()), "`delegate` is not a Delegate"); + uint balBefore = freshEscrow.balance(); + freshEscrow.onDeposit(); + assertEq(freshEscrow.market(), market); + assertEq(freshEscrow.beneficiary(), delegate); + assertEq(address(freshEscrow.token()), address(maker)); + assertEq(freshEscrow.delegate(), freshEscrow.beneficiary(), "Delegate not set"); + assertEq(balBefore, freshEscrow.balance(), "Balance after"); + } + function test_undelegate() public { deal(address(maker), address(escrowImplementation), 1 ether); assertEq(escrowImplementation.delegate(), address(0));