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

Op Farms #1

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0f1676f
chore: forge init
oldchili Jul 23, 2024
fb88fda
forge install: forge-std
oldchili Jul 23, 2024
6bc3351
forge install: dss-test
oldchili Jul 23, 2024
98153df
forge remove forge-std
oldchili Jul 23, 2024
d861e89
Remove Counter files
oldchili Jul 23, 2024
2d00826
Initial implementation
oldchili Jul 23, 2024
51f07cf
Gateway => Bridge
oldchili Jul 23, 2024
1e57f53
forge install: endgame-toolkit
oldchili Jul 23, 2024
1c4f38f
forge install: op-token-bridge
oldchili Jul 23, 2024
8f37ad4
Add init libs and integration test
oldchili Jul 31, 2024
5aab2da
Add scripts - untested
oldchili Jul 31, 2024
a3a6e93
Small changes
oldchili Aug 1, 2024
fecbab3
Script fixes and configs
oldchili Aug 1, 2024
ab03847
Remove unused interface, fix typo
oldchili Aug 5, 2024
73cbd87
Apply suggestions from code review
oldchili Aug 20, 2024
3db60d6
Bond cfg.minGasLimit and cfg.initMinGasLimit by 500M
oldchili Aug 20, 2024
fa068a6
Notes that gas limits need to be tighter in production
oldchili Aug 20, 2024
8902409
Add 20% gas buffer in Distribute.s.sol instructions
oldchili Aug 21, 2024
b08732f
Update dss-test
oldchili Aug 21, 2024
e665e0e
New line at EOF
oldchili Aug 21, 2024
74f503e
Update op-token-bridge
oldchili Aug 22, 2024
b5a90d0
Apply suggestions from code review
oldchili Aug 25, 2024
2212693
Remove redundant line
oldchili Aug 25, 2024
94fc23f
Inlinve some vars in script/Init.s.sol
oldchili Aug 25, 2024
29bc92a
More inlining
oldchili Aug 25, 2024
7aad28b
Update CI
oldchili Aug 25, 2024
632c17f
Minor changes in env example
oldchili Aug 26, 2024
4950996
Change TODO to clarification message
oldchili Aug 26, 2024
f71a30e
Update lib/op-token-bridge
oldchili Aug 26, 2024
119313b
Cantina audit (#2)
oldchili Sep 2, 2024
525f923
Add Cantina Report (#3)
oldchili Sep 10, 2024
6ce7a1e
Readme clarification about distribution rate (#4)
oldchili Sep 10, 2024
62c2dea
Add ChainSecurity Report (#5)
oldchili Sep 16, 2024
a78ed4d
Upgrade bridge dependency
sunbreak1211 Sep 17, 2024
ba2f7c7
Upgrade bridge dependency
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
Prev Previous commit
Next Next commit
Add init libs and integration test
oldchili committed Jul 31, 2024
commit 8f37ad4b7a7dee564ea97229393b8cae6119147a
5 changes: 2 additions & 3 deletions deploy/FarmProxyDeploy.sol
Original file line number Diff line number Diff line change
@@ -21,18 +21,17 @@ import { ScriptTools } from "dss-test/ScriptTools.sol";
import { L2FarmProxySpell } from "./L2FarmProxySpell.sol";
import { L1FarmProxy } from "src/L1FarmProxy.sol";
import { L2FarmProxy } from "src/L2FarmProxy.sol";
import { EtherForwarder } from "src/EtherForwarder.sol";

library FarmProxyDeploy {
function deployL1Proxy(
address deployer,
address owner,
address localToken,
address rewardsToken,
address remoteToken,
address l2Proxy,
address l1Bridge
) internal returns (address l1Proxy) {
l1Proxy = address(new L1FarmProxy(localToken, remoteToken, l2Proxy, l1Bridge));
l1Proxy = address(new L1FarmProxy(rewardsToken, remoteToken, l2Proxy, l1Bridge));
ScriptTools.switchOwner(l1Proxy, deployer, owner);
}

22 changes: 11 additions & 11 deletions deploy/FarmProxyInit.sol
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ interface VestedRewardsDistributionLike {
}

interface L1FarmProxyLike {
function localToken() external view returns (address);
function rewardsToken() external view returns (address);
function remoteToken() external view returns (address);
function l2Proxy() external view returns (address);
function l1Bridge() external view returns (address);
@@ -79,15 +79,15 @@ library FarmProxyInit {

// sanity checks

require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch");
require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch");
require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch");
require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch");
require(l1Proxy.localToken() == cfg.l1RewardsToken, "FarmProxyInit/local-token-mismatch");
require(l1Proxy.remoteToken() == cfg.l2RewardsToken, "FarmProxyInit/remote-token-mismatch");
require(l1Proxy.l1Bridge() == cfg.l1Bridge, "FarmProxyInit/l1-bridge-mismatch");
require(cfg.minGasLimit <= 10_000_000_000, "FarmProxyInit/min-gas-limit-out-of-bounds");
require(cfg.rewardThreshold <= type(uint224).max, "FarmProxyInit/reward-threshold-out-of-bounds");
require(vest.gem() == cfg.l1RewardsToken, "FarmProxyInit/vest-gem-mismatch");
require(distribution.gem() == cfg.l1RewardsToken, "FarmProxyInit/distribution-gem-mismatch");
require(distribution.stakingRewards() == l1Proxy_, "FarmProxyInit/distribution-farm-mismatch");
require(distribution.dssVest() == cfg.vest, "FarmProxyInit/distribution-vest-mismatch");
require(l1Proxy.rewardsToken() == cfg.l1RewardsToken, "FarmProxyInit/rewardsToken-token-mismatch");
require(l1Proxy.remoteToken() == cfg.l2RewardsToken, "FarmProxyInit/remote-token-mismatch");
require(l1Proxy.l1Bridge() == cfg.l1Bridge, "FarmProxyInit/l1-bridge-mismatch");
require(cfg.minGasLimit <= 10_000_000_000, "FarmProxyInit/min-gas-limit-out-of-bounds");
require(cfg.rewardThreshold <= type(uint224).max, "FarmProxyInit/reward-threshold-out-of-bounds");

// setup vest

@@ -119,7 +119,7 @@ library FarmProxyInit {
cfg.rewardThreshold,
cfg.rewardsDuration
)),
minGasLimit: relayMinGasLimit
minGasLimit: cfg.relayMinGasLimit
});

// update chainlog
2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
forge-std/=lib/dss-test/lib/forge-std/src/
openzeppelin-contracts/=lib/endgame-toolkit/lib/openzeppelin-contracts/contracts/
13 changes: 13 additions & 0 deletions script/input/1/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"domains": {
"mainnet": {
"chainlog": "0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F",
"tokens": []
},
"base": {
"l1Messenger": "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa",
"l2Messenger": "0x4200000000000000000000000000000000000007",
"tokens": []
}
}
}
9 changes: 9 additions & 0 deletions script/input/11155111/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"domains": {
"sepolia": {},
"base_sepolia": {
"l1Messenger": "0xC34855F4De64F1840e5686e64278da901e261f20",
"l2Messenger": "0x4200000000000000000000000000000000000007"
}
}
}
12 changes: 6 additions & 6 deletions src/L1FarmProxy.sol
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ contract L1FarmProxy {
uint32 public minGasLimit;
uint224 public rewardThreshold;

address public immutable localToken;
address public immutable rewardsToken;
address public immutable remoteToken;
address public immutable l2Proxy;
L1TokenBridgeLike public immutable l1Bridge;
@@ -48,13 +48,13 @@ contract L1FarmProxy {
event File(bytes32 indexed what, uint256 data);
event RewardAdded(uint256 reward);

constructor(address _localToken, address _remoteToken, address _l2Proxy, address _l1Bridge) {
localToken = _localToken;
constructor(address _rewardsToken, address _remoteToken, address _l2Proxy, address _l1Bridge) {
rewardsToken = _rewardsToken;
remoteToken = _remoteToken;
l2Proxy = _l2Proxy;
l1Bridge = L1TokenBridgeLike(_l1Bridge);
l1Bridge = L1TokenBridgeLike(_l1Bridge);

GemLike(_localToken).approve(_l1Bridge, type(uint256).max);
GemLike(_rewardsToken).approve(_l1Bridge, type(uint256).max);

wards[msg.sender] = 1;
emit Rely(msg.sender);
@@ -88,7 +88,7 @@ contract L1FarmProxy {
require(reward > rewardThreshold_, "L1FarmProxy/reward-too-small");

l1Bridge.bridgeERC20To({
_localToken: localToken,
_localToken: rewardsToken,
_remoteToken: remoteToken,
_to : l2Proxy,
_amount : reward,
256 changes: 256 additions & 0 deletions test/Integration.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// 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 { Domain } from "dss-test/domains/Domain.sol";
import { OptimismDomain } from "dss-test/domains/OptimismDomain.sol";

import { TokenBridgeDeploy } from "lib/op-token-bridge/deploy/TokenBridgeDeploy.sol";
import { L2TokenBridgeSpell } from "lib/op-token-bridge/deploy/L2TokenBridgeSpell.sol";
import { L1TokenBridgeInstance } from "lib/op-token-bridge/deploy/L1TokenBridgeInstance.sol";
import { L2TokenBridgeInstance } from "lib/op-token-bridge/deploy/L2TokenBridgeInstance.sol";
import { TokenBridgeInit, BridgesConfig } from "lib/op-token-bridge/deploy/TokenBridgeInit.sol";
import { StakingRewards, StakingRewardsDeploy, StakingRewardsDeployParams } from "lib/endgame-toolkit/script/dependencies/StakingRewardsDeploy.sol";
import { VestedRewardsDistributionDeploy, VestedRewardsDistributionDeployParams } from "lib/endgame-toolkit/script/dependencies/VestedRewardsDistributionDeploy.sol";
import { VestedRewardsDistribution } from "lib/endgame-toolkit/src/VestedRewardsDistribution.sol";
import { GemMock } from "test/mocks/GemMock.sol";
import { DssVestMintableMock } from "test/mocks/DssVestMock.sol";
import { FarmProxyDeploy } from "deploy/FarmProxyDeploy.sol";
import { L2FarmProxySpell } from "deploy/L2FarmProxySpell.sol";
import { FarmProxyInit, ProxiesConfig } from "deploy/FarmProxyInit.sol";
import { L1FarmProxy } from "src/L1FarmProxy.sol";
import { L2FarmProxy } from "src/L2FarmProxy.sol";

interface L1RelayLike {
function l2GovernanceRelay() external view returns (address);
}

contract IntegrationTest is DssTest {
string config;
Domain l1Domain;
OptimismDomain l2Domain;

// L1-side
DssInstance dss;
address PAUSE_PROXY;
address escrow;
address l1GovRelay;
address l1Messenger;
GemMock l1Token;
address l1Bridge;
L1FarmProxy l1Proxy;
DssVestMintableMock vest;
uint256 vestId;
VestedRewardsDistribution vestedRewardsDistribution;

// L2-side
address l2GovRelay;
address l2Messenger;
GemMock l2Token;
address l2Bridge;
L2FarmProxy l2Proxy;
StakingRewards farm;

constructor() {
vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); // used by ScriptTools to determine config path
// Note: need to set the domains here instead of in setUp() to make sure their storages are actually persistent
config = ScriptTools.loadConfig("config");

l1Domain = new Domain(config, getChain("mainnet"));
l2Domain = new OptimismDomain(config, getChain("base"), l1Domain);
}

function setupGateways() internal {
l1Messenger = address(l2Domain.l1Messenger());
l2Messenger = address(l2Domain.l2Messenger());

l1GovRelay = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 3); // foundry increments a global nonce across domains
l1Bridge = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 5);

l2Domain.selectFork();
L2TokenBridgeInstance memory l2BridgeInstance = TokenBridgeDeploy.deployL2Bridge({
deployer: address(this),
l1GovRelay: l1GovRelay,
l1Bridge: l1Bridge,
l2Messenger: l2Messenger
});
l2Bridge = l2BridgeInstance.bridge;
l2GovRelay = l2BridgeInstance.govRelay;

assertEq(address(L2TokenBridgeSpell(l2BridgeInstance.spell).l2Bridge()), address(l2Bridge));

l1Domain.selectFork();
L1TokenBridgeInstance memory l1BridgeInstance = TokenBridgeDeploy.deployL1Bridge({
deployer: address(this),
owner: PAUSE_PROXY,
l2GovRelay: l2GovRelay,
l2Bridge: address(l2Bridge),
l1Messenger: l1Messenger
});
assertEq(l1BridgeInstance.bridge, l1Bridge);
assertEq(l1BridgeInstance.govRelay, l1GovRelay);
escrow = l1BridgeInstance.escrow;

l2Domain.selectFork();
l2Token = new GemMock(0);
l2Token.rely(l2GovRelay);
l2Token.deny(address(this));

address[] memory l1Tokens = new address[](1);
l1Tokens[0] = address(l1Token);
address[] memory l2Tokens = new address[](1);
l2Tokens[0] = address(l2Token);

BridgesConfig memory cfg = BridgesConfig({
l1Messenger: l1Messenger,
l2Messenger: l2Messenger,
l1Tokens: l1Tokens,
l2Tokens: l2Tokens,
minGasLimit: 1_000_000,
govRelayCLKey: "BASE_GOV_RELAY",
escrowCLKey: "BASE_ESCROW",
l1BridgeCLKey: "BASE_TOKEN_BRIDGE"
});

l1Domain.selectFork();
vm.startPrank(PAUSE_PROXY);
TokenBridgeInit.initBridges(dss, l1BridgeInstance, l2BridgeInstance, cfg);
vm.stopPrank();

}

function setUp() public {
l1Domain.selectFork();
l1Domain.loadDssFromChainlog();
dss = l1Domain.dss();
PAUSE_PROXY = dss.chainlog.getAddress("MCD_PAUSE_PROXY");

vm.startPrank(PAUSE_PROXY);
l1Token = new GemMock(100 ether);
vest = new DssVestMintableMock(address(l1Token));
l1Token.rely(address(vest));
vest.file("cap", type(uint256).max);
vm.stopPrank();

setupGateways();

l2Domain.selectFork();

address stakingToken = address(new GemMock(100 ether));
StakingRewardsDeployParams memory farmParams = StakingRewardsDeployParams({
owner: l2GovRelay,
stakingToken: stakingToken,
rewardsToken: address(l2Token)
});
farm = StakingRewards(StakingRewardsDeploy.deploy(farmParams));

l2Proxy = L2FarmProxy(FarmProxyDeploy.deployL2Proxy({
deployer: address(this),
owner: l2GovRelay,
farm: address(farm)
}));
address l2Spell = FarmProxyDeploy.deployL2ProxySpell();

l1Domain.selectFork();
l1Proxy = L1FarmProxy(FarmProxyDeploy.deployL1Proxy({
deployer: address(this),
owner: PAUSE_PROXY,
rewardsToken: address(l1Token),
remoteToken: address(l2Token),
l2Proxy: address(l2Proxy),
l1Bridge: l1Bridge
}));

VestedRewardsDistributionDeployParams memory distributionParams = VestedRewardsDistributionDeployParams({
deployer: address(this),
owner: PAUSE_PROXY,
vest: address(vest),
rewards: address(l1Proxy)
});
vestedRewardsDistribution = VestedRewardsDistribution(VestedRewardsDistributionDeploy.deploy(distributionParams));

ProxiesConfig memory cfg = ProxiesConfig({
vest: address(vest),
vestTot: 100 * 1e18,
vestBgn: block.timestamp,
vestTau: 100 days,
vestedRewardsDistribution: address(vestedRewardsDistribution),
l1RewardsToken: address(l1Token),
l2RewardsToken: address(l2Token),
stakingToken: stakingToken,
l1Bridge: l1Bridge,
minGasLimit: 1_000_000, // determined by running deploy/Estimate.s.sol and adding some margin // TODO: leave this comment?
rewardThreshold: 1 ether,
farm: address(farm),
rewardsDuration: 1 days,
relayMinGasLimit: 1_000_000,
proxyChainlogKey: "FARM_PROXY_TKA_TKB_ARB",
distrChainlogKey: "REWARDS_DISTRIBUTION_TKA_TKB_ARB"
});

vm.startPrank(PAUSE_PROXY);
FarmProxyInit.initProxies(dss, l1GovRelay, address(l1Proxy), address(l2Proxy), l2Spell, cfg);
vm.stopPrank();

// test L1 side of initProxies
vestId = vestedRewardsDistribution.vestId();
assertEq(vest.usr(vestId), cfg.vestedRewardsDistribution);
assertEq(vest.bgn(vestId), cfg.vestBgn);
assertEq(vest.clf(vestId), cfg.vestBgn);
assertEq(vest.fin(vestId), cfg.vestBgn + cfg.vestTau);
assertEq(vest.tot(vestId), cfg.vestTot);
assertEq(vest.mgr(vestId), address(0));
assertEq(vest.res(vestId), 1);
assertEq(l1Proxy.minGasLimit(), cfg.minGasLimit);
assertEq(l1Proxy.rewardThreshold(), cfg.rewardThreshold);
assertEq(dss.chainlog.getAddress("FARM_PROXY_TKA_TKB_ARB"), address(l1Proxy));
assertEq(dss.chainlog.getAddress("REWARDS_DISTRIBUTION_TKA_TKB_ARB"), cfg.vestedRewardsDistribution);

l2Domain.relayFromHost(true);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just consider this relayFromHost would also be executing the L2 side of the spell for the Bridge set up. Nothing really wrong with that but doesn't get isolated in the setupGateways function as was probably the intention.

Copy link
Collaborator Author

@oldchili oldchili Aug 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is the same as in arbitrum-farms.
For some reason when adding l2Domain.relayFromHost(false); at the end of setupGateways the test fails with a message of "revert: CrossDomainMessenger: message has already been relayed".
I think @telome saw something similar, but not sure.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok yeah let's leave this open for now. It doesn't affect the audit as not in the scope anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not sure what exactly is the root cause of the issue but there seems to be some bug in foundry that causes the exit from setUp() to incorrectly restore the storage of OptimismDomain. For now a simple workaround could be to avoid using setUp(). For example, renaming setUp() to setUpFarm() and calling setUpFarm() at the beginning of distribute() avoids the issue.


// test L2 side of initProxies
assertEq(l2Proxy.rewardThreshold(), cfg.rewardThreshold);
assertEq(farm.rewardsDistribution(), address(l2Proxy));
assertEq(farm.rewardsDuration(), cfg.rewardsDuration);
}

function testDistribution() public {
l1Domain.selectFork();
uint256 rewardThreshold = l1Proxy.rewardThreshold();
vm.warp(vest.bgn(vestId) + rewardThreshold * (vest.fin(vestId) - vest.bgn(vestId)) / vest.tot(vestId) + 1);
uint256 amount = vest.unpaid(vestId);
assertGt(amount, rewardThreshold);
assertEq(l1Token.balanceOf(escrow), 0);

vestedRewardsDistribution.distribute();

assertEq(l1Token.balanceOf(escrow), amount);

l2Domain.relayFromHost(true);

assertEq(l2Token.balanceOf(address(l2Proxy)), amount);

l2Proxy.forwardReward();

assertEq(l2Token.balanceOf(address(l2Proxy)), 0);
assertEq(l2Token.balanceOf(address(farm)), amount);
assertEq(farm.rewardRate(), amount / farm.rewardsDuration());
}
}
34 changes: 17 additions & 17 deletions test/L1FarmProxy.t.sol
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ import { L1TokenBridgeMock } from "test/mocks/L1TokenBridgeMock.sol";

contract L1FarmProxyTest is DssTest {

GemMock localToken;
GemMock rewardsToken;
L1FarmProxy l1Proxy;
address bridge;
address escrow = address(0xeee);
@@ -36,20 +36,20 @@ contract L1FarmProxyTest is DssTest {

function setUp() public {
bridge = address(new L1TokenBridgeMock(escrow));
localToken = new GemMock(1_000_000 ether);
l1Proxy = new L1FarmProxy(address(localToken), remoteToken, l2Proxy, bridge);
rewardsToken = new GemMock(1_000_000 ether);
l1Proxy = new L1FarmProxy(address(rewardsToken), remoteToken, l2Proxy, bridge);
}

function testConstructor() public {
vm.expectEmit(true, true, true, true);
emit Rely(address(this));
L1FarmProxy p = new L1FarmProxy(address(localToken), remoteToken, l2Proxy, bridge);
L1FarmProxy p = new L1FarmProxy(address(rewardsToken), remoteToken, l2Proxy, bridge);

assertEq(p.localToken(), address(localToken));
assertEq(p.rewardsToken(), address(rewardsToken));
assertEq(p.remoteToken(), remoteToken);
assertEq(p.l2Proxy(), l2Proxy);
assertEq(address(p.l1Bridge()), bridge);
assertEq(localToken.allowance(address(p), bridge), type(uint256).max);
assertEq(rewardsToken.allowance(address(p), bridge), type(uint256).max);
assertEq(p.wards(address(this)), 1);
}

@@ -71,15 +71,15 @@ contract L1FarmProxyTest is DssTest {

function testRecover() public {
address receiver = address(0x123);
localToken.transfer(address(l1Proxy), 1 ether);
rewardsToken.transfer(address(l1Proxy), 1 ether);

assertEq(localToken.balanceOf(receiver), 0);
assertEq(localToken.balanceOf(address(l1Proxy)), 1 ether);
assertEq(rewardsToken.balanceOf(receiver), 0);
assertEq(rewardsToken.balanceOf(address(l1Proxy)), 1 ether);

l1Proxy.recover(address(localToken), receiver, 1 ether);
l1Proxy.recover(address(rewardsToken), receiver, 1 ether);

assertEq(localToken.balanceOf(receiver), 1 ether);
assertEq(localToken.balanceOf(address(l1Proxy)), 0);
assertEq(rewardsToken.balanceOf(receiver), 1 ether);
assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0);
}

function testNotifyRewardAmount() public {
@@ -88,15 +88,15 @@ contract L1FarmProxyTest is DssTest {
vm.expectRevert("L1FarmProxy/reward-too-small");
l1Proxy.notifyRewardAmount(100 ether);

localToken.transfer(address(l1Proxy), 101 ether);
assertEq(localToken.balanceOf(escrow), 0);
assertEq(localToken.balanceOf(address(l1Proxy)), 101 ether);
rewardsToken.transfer(address(l1Proxy), 101 ether);
assertEq(rewardsToken.balanceOf(escrow), 0);
assertEq(rewardsToken.balanceOf(address(l1Proxy)), 101 ether);

vm.expectEmit(true, true, true, true);
emit RewardAdded(101 ether);
l1Proxy.notifyRewardAmount(101 ether);

assertEq(localToken.balanceOf(escrow), 101 ether);
assertEq(localToken.balanceOf(address(l1Proxy)), 0);
assertEq(rewardsToken.balanceOf(escrow), 101 ether);
assertEq(rewardsToken.balanceOf(address(l1Proxy)), 0);
}
}