Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add L1 & L2 Farm Proxies #1

Open
wants to merge 58 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
21f73d7
Add farm proxies
May 22, 2024
c8febe2
Add missing approval
May 22, 2024
afb473c
Add e2e test & deploy scripts
May 24, 2024
2a65e8c
Update new-bridge
May 24, 2024
79246a0
Add default maxGas estimate
May 31, 2024
45669be
Add unit tests
May 31, 2024
4ef9be8
Add vest setup to init script
May 31, 2024
5f42034
Add maxGas estimation script
Jun 5, 2024
4cede7b
Add minReward to L1Proxy
Jun 13, 2024
f88061e
Revert when reward is too small on L1
Jun 14, 2024
d98c65a
Add estimateDepositCost view func
Jun 14, 2024
e1c107b
Apply suggestions from code review
telome Jun 19, 2024
b498cd9
Update arbitrum-token-bridge
Jun 24, 2024
ad7048c
Apply suggestions from code review
telome Jun 24, 2024
c86e45e
File gas params in spell
Jun 24, 2024
c2eb5ab
Update arbitrum-token-bridge
Jun 25, 2024
f371a1a
Add more sanity checks
Jun 25, 2024
9b4847b
Assume dss-vest already inited
Jun 25, 2024
63e4ca2
Update chainlog
Jun 25, 2024
84e2b6a
Make L2 spell reusable across L2 proxies
Jun 25, 2024
6044525
Add farm sanity checks
Jun 25, 2024
87a2147
Add more farm checks
Jun 25, 2024
cf292ac
Fix nits
Jun 25, 2024
8fb5adf
Apply suggestions from code review
telome Jun 25, 2024
71829f9
Add Deploy.s.sol
Jun 26, 2024
214a78a
Add Init.s.sol
Jun 26, 2024
9e2b461
Fix tests and apply correct alias to feeRecipient
Jun 27, 2024
6ba7328
Move feeRecipient aliasing to init lib
Jun 27, 2024
2229c3a
Move dealiasing to L1FarmProxy
Jun 27, 2024
2093296
Add deploy test scripts and fix nits
Jun 27, 2024
1441c87
Update script/output/11155111/deployed-latest.json
telome Jun 27, 2024
8202396
Fix Estimate script
Jul 1, 2024
7777054
Use minThreshold
Jul 2, 2024
84680b8
emit RewardAdded from L1 proxy
Jul 5, 2024
1376638
Complete README
Jul 9, 2024
a8c1570
Remove vestMgr
Jul 9, 2024
d356c1d
Update arbitrum-token-bridge
Jul 9, 2024
e36873e
Check vest mgr is still 0 after spell
Jul 10, 2024
25c8104
Add token recovery funcs
Jul 10, 2024
c4d9f08
Recommend L2FarmProxy.rewardThreshold <= L1FarmProxy.rewardThreshold
Jul 10, 2024
99af088
Use single rewardThreshold in init lib
Jul 10, 2024
fbe197f
Fix L2 spell and add L2 spell tests
Jul 10, 2024
2960565
Fix alignment
Jul 10, 2024
f74d47a
Fix spell revert msg
Jul 10, 2024
7226d12
Fix nits
Jul 11, 2024
78aa99d
Fix excess fee collection logic
Jul 13, 2024
a45c297
Remove old Deploy.s.sol
Jul 13, 2024
880c283
Remove extra space
Jul 15, 2024
3e6f5c7
Merge pull request #2 from telome/fee-forwarder
telome Jul 16, 2024
d8204ad
Update README for env vars to be exported
Jul 16, 2024
09ce9d8
Use single deploy proxy script with multiple deployers
Jul 18, 2024
702e425
Remove unnecessary command
Jul 18, 2024
fa18c20
Update deps
Jul 18, 2024
38476c1
Remove outdated files
Jul 18, 2024
69b539f
Add CS report (#3)
telome Aug 23, 2024
8cae0ca
Update deps (#4)
telome Aug 23, 2024
b879549
Update CI
Aug 23, 2024
370f3da
Upgrade bridge dependency + minor adjustment (#5)
sunbreak1211 Sep 19, 2024
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
3 changes: 2 additions & 1 deletion deploy/L2FarmProxySpell.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface L2FarmProxyLike {
function rely(address) external;
function deny(address) external;
function file(bytes32, uint256) external;
function recover(address token, address to, uint256 amount) external;
}

interface FarmLike {
Expand All @@ -39,6 +40,7 @@ contract L2FarmProxySpell {
function rely(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).rely(usr); }
function deny(address l2Proxy, address usr) external { L2FarmProxyLike(l2Proxy).deny(usr); }
function file(address l2Proxy, bytes32 what, uint256 data) external { L2FarmProxyLike(l2Proxy).file(what, data); }
oldchili marked this conversation as resolved.
Show resolved Hide resolved
function recover(address l2Proxy, address token, address to, uint256 amount) external { L2FarmProxyLike(l2Proxy).recover(token, to, amount); }

function nominateNewOwner(address farm, address owner) external { FarmLike(farm).nominateNewOwner(owner); }
function setPaused(address farm, bool paused) external { FarmLike(farm).setPaused(paused); }
Expand All @@ -57,7 +59,6 @@ contract L2FarmProxySpell {
// sanity checks
require(L2FarmProxyLike(l2Proxy).rewardsToken() == rewardsToken, "L2FarmProxySpell/rewards-token-mismatch");
require(L2FarmProxyLike(l2Proxy).farm() == farm, "L2FarmProxySpell/farm-mismatch");
require(FarmLike(farm).rewardsToken() == rewardsToken, "L2FarmProxySpell/farm-rewards-token-mismatch");
sunbreak1211 marked this conversation as resolved.
Show resolved Hide resolved
require(FarmLike(farm).stakingToken() == stakingToken, "L2FarmProxySpell/farm-rewards-token-mismatch");
oldchili marked this conversation as resolved.
Show resolved Hide resolved
require(stakingToken != rewardsToken, "L2FarmProxySpell/rewards-token-same-as-staking-token");

oldchili marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
2 changes: 1 addition & 1 deletion test/L2FarmProxy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract L2FarmProxyTest is DssTest {

function setUp() public {
rewardsToken = new GemMock(1_000_000 ether);
farm = address(new FarmMock(address(rewardsToken)));
farm = address(new FarmMock(address(rewardsToken), address(123)));
l2Proxy = new L2FarmProxy(farm);
}

Expand Down
180 changes: 180 additions & 0 deletions test/L2FarmProxySpell.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

// Copyright (C) 2024 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.8.21;

import "dss-test/DssTest.sol";

import { L2FarmProxy } from "src/L2FarmProxy.sol";
import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol";
import { FarmMock } from "test/mocks/FarmMock.sol";
import { GemMock } from "test/mocks/GemMock.sol";

contract L2FarmProxySpellTest is DssTest {

GemMock rewardsToken;
address stakingToken = address(444);
address l2Proxy;
L2FarmProxySpell l2Spell;
address farm;

event OwnerNominated(address newOwner);
event PauseChanged(bool isPaused);
event RewardsDurationUpdated(uint256 newDuration);
event RewardsDistributionUpdated(address newRewardsDistribution);
event Recovered(address token, uint256 amount);

function setUp() public {
rewardsToken = new GemMock(1_000_000 ether);
farm = address(new FarmMock(address(rewardsToken), stakingToken));
l2Proxy = address(new L2FarmProxy(farm));
l2Spell = new L2FarmProxySpell();
}

function testL2ProxyFunctions() public {
bool success;
address usr = address(123);

vm.expectEmit(true, true, true, true);
emit Rely(usr);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.rely, (l2Proxy, usr)));
assertTrue(success);

vm.expectEmit(true, true, true, true);
emit Deny(usr);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.deny, (l2Proxy, usr)));
assertTrue(success);

bytes32 what = "rewardThreshold";
uint256 data = 456;
vm.expectEmit(true, true, true, true);
emit File(what, data);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.file, (l2Proxy, what, data)));
assertTrue(success);

uint256 amount = 789 ether;
rewardsToken.transfer(l2Proxy, amount);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.recover, (l2Proxy, address(rewardsToken), usr, amount)));
assertTrue(success);
assertEq(rewardsToken.balanceOf(usr), amount);
}

function testFarmFunctions() public {
bool success;
address usr = address(123);

vm.expectEmit(true, true, true, true);
emit OwnerNominated(usr);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.nominateNewOwner, (farm, usr)));
assertTrue(success);

vm.expectEmit(true, true, true, true);
emit PauseChanged(true);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.setPaused, (farm, true)));
assertTrue(success);

uint256 amount = 456 ether;
vm.expectEmit(true, true, true, true);
emit Recovered(address(rewardsToken), amount);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.recoverERC20, (farm, address(rewardsToken), amount)));
assertTrue(success);

vm.expectEmit(true, true, true, true);
emit RewardsDurationUpdated(amount);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.setRewardsDuration, (farm, amount)));
assertTrue(success);

vm.expectEmit(true, true, true, true);
emit RewardsDistributionUpdated(usr);
(success,) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.setRewardsDistribution, (farm, usr)));
assertTrue(success);
}

// from https://ethereum.stackexchange.com/a/83577
function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) {
if (_returnData.length < 68) return 'Transaction reverted silently';
assembly { _returnData := add(_returnData, 0x04) }
return abi.decode(_returnData, (string));
}

function testInit() public {
bool success;
bytes memory response;

(success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, (
l2Proxy,
address(0xb4d),
stakingToken,
farm,
0,
7 days
)));
assertFalse(success);
assertEq(_getRevertMsg(response), "L2FarmProxySpell/rewards-token-mismatch");

(success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, (
l2Proxy,
address(rewardsToken),
stakingToken,
address(0xb4d),
0,
7 days
)));
assertFalse(success);
assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-mismatch");

(success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, (
l2Proxy,
address(rewardsToken),
address(0xb4d),
farm,
0,
7 days
)));
assertFalse(success);
assertEq(_getRevertMsg(response), "L2FarmProxySpell/farm-rewards-token-mismatch");

address badFarm = address(new FarmMock(address(rewardsToken), address(rewardsToken)));
address badL2Proxy = address(new L2FarmProxy(badFarm));
(success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, (
badL2Proxy,
address(rewardsToken),
address(rewardsToken),
badFarm,
0,
7 days
)));
assertFalse(success);
assertEq(_getRevertMsg(response), "L2FarmProxySpell/rewards-token-same-as-staking-token");

vm.expectEmit(true, true, true, true);
emit File("rewardThreshold", 888);
vm.expectEmit(true, true, true, true);
emit RewardsDistributionUpdated(l2Proxy);
vm.expectEmit(true, true, true, true);
emit RewardsDurationUpdated(7 days);
(success, response) = address(l2Spell).delegatecall(abi.encodeCall(L2FarmProxySpell.init, (
l2Proxy,
address(rewardsToken),
stakingToken,
farm,
888,
7 days
)));
assertTrue(success);
}
}
30 changes: 29 additions & 1 deletion test/mocks/FarmMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,41 @@ pragma solidity ^0.8.21;

contract FarmMock {
address public immutable rewardsToken;
address public immutable stakingToken;

event OwnerNominated(address newOwner);
event PauseChanged(bool isPaused);
event RewardAdded(uint256 rewards);
event RewardsDurationUpdated(uint256 newDuration);
event RewardsDistributionUpdated(address newRewardsDistribution);
event Recovered(address token, uint256 amount);

constructor(address _rewardsToken) {
constructor(address _rewardsToken, address _stakingToken) {
rewardsToken = _rewardsToken;
stakingToken = _stakingToken;
}

function nominateNewOwner(address _owner) external {
emit OwnerNominated(_owner);
}

function setPaused(bool _paused) external {
emit PauseChanged(_paused);
}

function notifyRewardAmount(uint256 reward) external {
emit RewardAdded(reward);
}

function recoverERC20(address tokenAddress, uint256 tokenAmount) external {
emit Recovered(tokenAddress, tokenAmount);
}

function setRewardsDuration(uint256 _rewardsDuration) external {
emit RewardsDurationUpdated(_rewardsDuration);
}

function setRewardsDistribution(address _rewardsDistribution) external {
emit RewardsDistributionUpdated(_rewardsDistribution);
}
}