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

Link liquidity pool, add withdraw functions for finance team and some refactoring #12375

Merged
merged 5 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/swift-bobcats-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

add liquidity pool for automation 2.3

Large diffs are not rendered by default.

154 changes: 151 additions & 3 deletions contracts/src/v0.8/automation/dev/test/AutomationRegistry2_3.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {AutomationRegistryLogicB2_3} from "../v2_3/AutomationRegistryLogicB2_3.s
import {IAutomationRegistryMaster2_3, AutomationRegistryBase2_3} from "../interfaces/v2_3/IAutomationRegistryMaster2_3.sol";
import {ChainModuleBase} from "../../chains/ChainModuleBase.sol";
import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol";
import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol";

contract AutomationRegistry2_3_SetUp is BaseTest {
address internal LINK_USD_FEED;
address internal NATIVE_USD_FEED;
address internal FAST_GAS_FEED;
address internal constant LINK_TOKEN = 0x1111111111111111111111111111111111111113;
address internal constant FINANCE_ADMIN_ADDRESS = 0x1111111111111111111111111111111111111114;
address internal constant ZERO_ADDRESS = address(0);
address internal constant UPKEEP_ADMIN = address(uint160(uint256(keccak256("ADMIN"))));

// Signer private keys used for these test
uint256 internal constant PRIVATE0 = 0x7b2e97fe057e6de99d6872a2ef2abf52c9b4469bc848c2465ac3fcd8d336e81d;
Expand All @@ -31,12 +33,17 @@ contract AutomationRegistry2_3_SetUp is BaseTest {
address[] internal s_registrars;

IAutomationRegistryMaster2_3 internal registryMaster;
ERC20Mock internal link; // the link token
ERC20Mock internal mockERC20; // the supported ERC20 tokens except link

function setUp() public override {
LINK_USD_FEED = address(new MockV3Aggregator(8, 2_000_000_000)); // $20
NATIVE_USD_FEED = address(new MockV3Aggregator(8, 400_000_000_000)); // $4,000
FAST_GAS_FEED = address(new MockV3Aggregator(0, 1_000_000_000)); // 1 gwei

link = new ERC20Mock("LINK", "LINK", UPKEEP_ADMIN, 0);
mockERC20 = new ERC20Mock("MOCK_ERC20", "MOCK_ERC20", UPKEEP_ADMIN, 0);

s_valid_transmitters = new address[](4);
for (uint160 i = 0; i < 4; ++i) {
s_valid_transmitters[i] = address(4 + i);
Expand All @@ -53,7 +60,7 @@ contract AutomationRegistry2_3_SetUp is BaseTest {

AutomationForwarderLogic forwarderLogic = new AutomationForwarderLogic();
AutomationRegistryLogicB2_3 logicB2_3 = new AutomationRegistryLogicB2_3(
LINK_TOKEN,
address(link),
LINK_USD_FEED,
NATIVE_USD_FEED,
FAST_GAS_FEED,
Expand Down Expand Up @@ -88,6 +95,146 @@ contract AutomationRegistry2_3_CheckUpkeep is AutomationRegistry2_3_SetUp {
}
}

contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
address internal aMockAddress = address(0x1111111111111111111111111111111111111113);

function mintLink(address recipient, uint256 amount) public {
vm.prank(UPKEEP_ADMIN);
//mint the link to the recipient
link.mint(recipient, amount);
}

function mintERC20(address recipient, uint256 amount) public {
vm.prank(UPKEEP_ADMIN);
//mint the ERC20 to the recipient
mockERC20.mint(recipient, amount);
}

function setConfigForWithdraw() public {
RyanRHall marked this conversation as resolved.
Show resolved Hide resolved
address module = address(new ChainModuleBase());
AutomationRegistryBase2_3.OnchainConfig memory cfg = AutomationRegistryBase2_3.OnchainConfig({
paymentPremiumPPB: 10_000,
flatFeeMicroLink: 40_000,
checkGasLimit: 5_000_000,
stalenessSeconds: 90_000,
gasCeilingMultiplier: 0,
minUpkeepSpend: 0,
maxPerformGas: 10_000_000,
maxCheckDataSize: 5_000,
maxPerformDataSize: 5_000,
maxRevertDataSize: 5_000,
fallbackGasPrice: 20_000_000_000,
fallbackLinkPrice: 2_000_000_000, // $20
fallbackNativePrice: 400_000_000_000, // $4,000
transcoder: 0xB1e66855FD67f6e85F0f0fA38cd6fBABdf00923c,
registrars: s_registrars,
upkeepPrivilegeManager: 0xD9c855F08A7e460691F41bBDDe6eC310bc0593D8,
chainModule: module,
reorgProtectionEnabled: true,
financeAdmin: FINANCE_ADMIN_ADDRESS
});
bytes memory offchainConfigBytes = abi.encode(1234, ZERO_ADDRESS);

registryMaster.setConfigTypeSafe(
s_valid_signers,
s_valid_transmitters,
F,
cfg,
OFFCHAIN_CONFIG_VERSION,
offchainConfigBytes,
new address[](0),
new AutomationRegistryBase2_3.BillingConfig[](0)
);
}

function testLinkAvailableForPaymentReturnsLinkBalance() public {
//simulate a deposit of link to the liquidity pool
mintLink(address(registryMaster), 1e10);

//check there's a balance
assertGt(link.balanceOf(address(registryMaster)), 0);

//check the link available for payment is the link balance
assertEq(registryMaster.linkAvailableForPayment(), link.balanceOf(address(registryMaster)));
}

function testWithdrawLinkFeesRevertsBecauseOnlyFinanceAdminAllowed() public {
// set config with the finance admin
setConfigForWithdraw();

vm.expectRevert(abi.encodeWithSelector(IAutomationRegistryMaster2_3.OnlyFinanceAdmin.selector));
registryMaster.withdrawLinkFees(aMockAddress, 1);
}

function testWithdrawLinkFeesRevertsBecauseOfInsufficientBalance() public {
// set config with the finance admin
setConfigForWithdraw();

vm.startPrank(FINANCE_ADMIN_ADDRESS);

// try to withdraw 1 link while there is 0 balance
vm.expectRevert(abi.encodeWithSelector(IAutomationRegistryMaster2_3.InsufficientBalance.selector, 0, 1));
registryMaster.withdrawLinkFees(aMockAddress, 1);

vm.stopPrank();
}

function testWithdrawLinkFeesRevertsBecauseOfInvalidRecipient() public {
// set config with the finance admin
setConfigForWithdraw();

vm.startPrank(FINANCE_ADMIN_ADDRESS);

// try to withdraw 1 link while there is 0 balance
vm.expectRevert(abi.encodeWithSelector(IAutomationRegistryMaster2_3.InvalidRecipient.selector));
registryMaster.withdrawLinkFees(ZERO_ADDRESS, 1);

vm.stopPrank();
}

function testWithdrawLinkFeeSuccess() public {
// set config with the finance admin
setConfigForWithdraw();

//simulate a deposit of link to the liquidity pool
mintLink(address(registryMaster), 1e10);

//check there's a balance
assertGt(link.balanceOf(address(registryMaster)), 0);

vm.startPrank(FINANCE_ADMIN_ADDRESS);

// try to withdraw 1 link while there is a ton of link available
registryMaster.withdrawLinkFees(aMockAddress, 1);

vm.stopPrank();

assertEq(link.balanceOf(address(aMockAddress)), 1);
assertEq(link.balanceOf(address(registryMaster)), 1e10 - 1);
}

function testWithdrawERC20FeeSuccess() public {
// set config with the finance admin
setConfigForWithdraw();

// simulate a deposit of ERC20 to the liquidity pool
mintERC20(address(registryMaster), 1e10);

// check there's a balance
assertGt(mockERC20.balanceOf(address(registryMaster)), 0);

vm.startPrank(FINANCE_ADMIN_ADDRESS);

// try to withdraw 1 link while there is a ton of link available
registryMaster.withdrawERC20Fees(address(mockERC20), aMockAddress, 1);

vm.stopPrank();

assertEq(mockERC20.balanceOf(address(aMockAddress)), 1);
assertEq(mockERC20.balanceOf(address(registryMaster)), 1e10 - 1);
}
}

contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
event ConfigSet(
uint32 previousConfigBlockNumber,
Expand Down Expand Up @@ -121,7 +268,8 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
registrars: s_registrars,
upkeepPrivilegeManager: 0xD9c855F08A7e460691F41bBDDe6eC310bc0593D8,
chainModule: module,
reorgProtectionEnabled: true
reorgProtectionEnabled: true,
financeAdmin: FINANCE_ADMIN_ADDRESS
});

function testSetConfigSuccess() public {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ contract AutomationRegistry2_3 is AutomationRegistryBase2_3, OCR2Abstract, Chain
uint256 id = abi.decode(data, (uint256));
if (s_upkeep[id].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
s_upkeep[id].balance = s_upkeep[id].balance + uint96(amount);
s_expectedLinkBalance = s_expectedLinkBalance + amount;
s_reserveLinkBalance = s_reserveLinkBalance + amount;
emit FundsAdded(id, sender, uint96(amount));
}

Expand Down Expand Up @@ -357,10 +357,10 @@ contract AutomationRegistry2_3 is AutomationRegistryBase2_3, OCR2Abstract, Chain
maxPerformDataSize: onchainConfig.maxPerformDataSize,
maxRevertDataSize: onchainConfig.maxRevertDataSize,
upkeepPrivilegeManager: onchainConfig.upkeepPrivilegeManager,
financeAdmin: onchainConfig.financeAdmin,
nonce: s_storage.nonce,
configCount: s_storage.configCount,
latestConfigBlockNumber: s_storage.latestConfigBlockNumber,
ownerLinkBalance: s_storage.ownerLinkBalance
latestConfigBlockNumber: s_storage.latestConfigBlockNumber
});
s_fallbackGasPrice = onchainConfig.fallbackGasPrice;
s_fallbackLinkPrice = onchainConfig.fallbackLinkPrice;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
uint256 internal s_fallbackGasPrice;
uint256 internal s_fallbackLinkPrice;
uint256 internal s_fallbackNativePrice;
uint256 internal s_expectedLinkBalance; // Used in case of erroneous LINK transfers to contract
uint256 internal s_reserveLinkBalance; // Unspent user deposits + unwithdrawn NOP payments
RyanRHall marked this conversation as resolved.
Show resolved Hide resolved
mapping(address => MigrationPermission) internal s_peerRegistryMigrationPermission; // Permissions for migration to and fro
mapping(uint256 => bytes) internal s_upkeepTriggerConfig; // upkeep triggers
mapping(uint256 => bytes) internal s_upkeepOffchainConfig; // general config set by users for each upkeep
Expand All @@ -122,6 +122,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
error IncorrectNumberOfSignatures();
error IncorrectNumberOfSigners();
error IndexOutOfRange();
error InsufficientBalance(uint256 available, uint256 requested);
error InvalidDataLength();
error InvalidFeed();
error InvalidTrigger();
Expand All @@ -145,6 +146,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
error OnlyCallableByProposedAdmin();
error OnlyCallableByProposedPayee();
error OnlyCallableByUpkeepPrivilegeManager();
error OnlyFinanceAdmin();
error OnlyPausedUpkeep();
error OnlySimulatedBackend();
error OnlyUnpausedUpkeep();
Expand All @@ -157,6 +159,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
error TargetCheckReverted(bytes reason);
error TooManyOracles();
error TranscoderNotSet();
error TransferFailed();
error UpkeepAlreadyExists();
error UpkeepCancelled();
error UpkeepNotCanceled();
Expand Down Expand Up @@ -276,14 +279,14 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
address upkeepPrivilegeManager;
IChainModule chainModule;
bool reorgProtectionEnabled;
address financeAdmin; // TODO: pack this struct better
}

/**
* @notice state of the registry
* @dev only used in params and return values
* @dev this will likely be deprecated in a future version of the registry in favor of individual getters
* @member nonce used for ID generation
* @member ownerLinkBalance withdrawable balance of LINK by contract owner
* @member expectedLinkBalance the expected balance of LINK of the registry
* @member totalPremium the total premium collected on registry so far
* @member numUpkeeps total number of upkeeps on the registry
Expand Down Expand Up @@ -376,7 +379,6 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
uint96 minUpkeepSpend; // Minimum amount an upkeep must spend
address transcoder; // Address of transcoder contract used in migrations
// 1 EVM word full
uint96 ownerLinkBalance; // Balance of owner, accumulates minUpkeepSpend in case it is not spent
uint32 checkGasLimit; // Gas limit allowed in checkUpkeep
uint32 maxPerformGas; // Max gas an upkeep can use on this registry
uint32 nonce; // Nonce for each upkeep created
Expand All @@ -389,6 +391,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
uint32 maxRevertDataSize; // max length of revertData bytes
address upkeepPrivilegeManager; // address which can set privilege for upkeeps
// 3 EVM word full
address financeAdmin; // address which can withdraw funds from the contract
}

/// @dev Report transmitted by OCR to transmit function
Expand Down Expand Up @@ -501,7 +504,6 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
event FundsAdded(uint256 indexed id, address indexed from, uint96 amount);
event FundsWithdrawn(uint256 indexed id, uint256 amount, address to);
event InsufficientFundsUpkeepReport(uint256 indexed id, bytes trigger);
event OwnerFundsWithdrawn(uint96 amount);
event Paused(address account);
event PayeesUpdated(address[] transmitters, address[] payees);
event PayeeshipTransferRequested(address indexed transmitter, address indexed from, address indexed to);
Expand Down Expand Up @@ -533,6 +535,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
event Unpaused(address account);
// Event to emit when a billing configuration is set
event BillingConfigSet(IERC20 indexed token, BillingConfig config);
event FeesWithdrawn(address indexed recipient, address indexed assetAddress, uint256 amount);

/**
* @param link address of the LINK Token
Expand Down Expand Up @@ -590,7 +593,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
s_upkeep[id] = upkeep;
s_upkeepAdmin[id] = admin;
s_checkData[id] = checkData;
s_expectedLinkBalance = s_expectedLinkBalance + upkeep.balance;
s_reserveLinkBalance = s_reserveLinkBalance + upkeep.balance;
s_upkeepTriggerConfig[id] = triggerConfig;
s_upkeepOffchainConfig[id] = offchainConfig;
s_upkeepIDs.add(id);
Expand Down Expand Up @@ -1018,6 +1021,15 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
}
}

/**
* @notice only allows finance admin to call the function
*/
function _onlyFinanceAdminAllowed() internal view {
if (msg.sender != s_storage.financeAdmin) {
revert OnlyFinanceAdmin();
}
}

/**
* @notice sets billing configuration for a token
* @param billingTokens the addresses of tokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ contract AutomationRegistryLogicA2_3 is AutomationRegistryBase2_3, Chainable {
}
}
s_upkeep[id].balance = upkeep.balance - cancellationFee;
s_storage.ownerLinkBalance = s_storage.ownerLinkBalance + cancellationFee;
s_reserveLinkBalance = s_reserveLinkBalance - cancellationFee;

emit UpkeepCanceled(id, uint64(height));
}
Expand All @@ -309,7 +309,7 @@ contract AutomationRegistryLogicA2_3 is AutomationRegistryBase2_3, Chainable {
Upkeep memory upkeep = s_upkeep[id];
if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
s_upkeep[id].balance = upkeep.balance + amount;
s_expectedLinkBalance = s_expectedLinkBalance + amount;
s_reserveLinkBalance = s_reserveLinkBalance + amount;
i_link.transferFrom(msg.sender, address(this), amount);
emit FundsAdded(id, msg.sender, amount);
}
Expand Down Expand Up @@ -357,7 +357,7 @@ contract AutomationRegistryLogicA2_3 is AutomationRegistryBase2_3, Chainable {
s_upkeepIDs.remove(id);
emit UpkeepMigrated(id, upkeep.balance, destination);
}
s_expectedLinkBalance = s_expectedLinkBalance - totalBalanceRemaining;
s_reserveLinkBalance = s_reserveLinkBalance - totalBalanceRemaining;
bytes memory encodedUpkeeps = abi.encode(
ids,
upkeeps,
Expand Down
Loading
Loading