Skip to content

Commit

Permalink
Link liquidity pool, add withdraw functions for finance team and some…
Browse files Browse the repository at this point in the history
… refactoring (#12375)

* add financeAdmin to onchainConfig

* remove ownerLinkbalance and rename expectedLinkBalance

* add withdraw functions

* add foundry test and generate wrappers
  • Loading branch information
shileiwill authored and kidambisrinivas committed Mar 27, 2024
1 parent e45df07 commit 2513c33
Show file tree
Hide file tree
Showing 15 changed files with 878 additions and 561 deletions.
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 {
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
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

0 comments on commit 2513c33

Please sign in to comment.