Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into add-jfp-cache-adju…
Browse files Browse the repository at this point in the history
…stable-staleness-alert

# Conflicts:
#	core/services/ocrcommon/data_source.go
#	core/services/ocrcommon/data_source_test.go
  • Loading branch information
ilija42 committed Mar 26, 2024
2 parents 9910a86 + 6fb421d commit a6d385c
Show file tree
Hide file tree
Showing 31 changed files with 523 additions and 256 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-berries-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

fix bug in auto2.3 withdrawERC20Fees
5 changes: 5 additions & 0 deletions .changeset/silent-pets-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

Exposing information about LogPoller finality violation via Healthy method. It's raised whenever LogPoller sees reorg deeper than the finality
5 changes: 5 additions & 0 deletions .changeset/thin-coats-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

fix withdraw LINK bug in auto 2.3
5 changes: 5 additions & 0 deletions .changeset/wicked-gorillas-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

VRFV2PlusWrapper config refactor
2 changes: 1 addition & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ core/scripts/gateway @smartcontractkit/functions
/contracts/src/v0.8/l2ep @chris-de-leon-cll
/contracts/src/v0.8/llo-feeds @smartcontractkit/mercury-team
# TODO: mocks folder, folder should be removed and files moved to the correct folders
/contracts/src/v0.8/operatorforwarder @smartcontractkit/foundations
/contracts/src/v0.8/operatorforwarder @RensR
/contracts/src/v0.8/shared @RensR
# TODO: tests folder, folder should be removed and files moved to the correct folders
# TODO: transmission folder, owner should be found
Expand Down
5 changes: 5 additions & 0 deletions contracts/.changeset/early-hairs-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": patch
---

fix bug in auto2.3 withdrawERC20Fees
5 changes: 5 additions & 0 deletions contracts/.changeset/eight-peas-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": minor
---

VRFV2PlusWrapper config refactor
5 changes: 5 additions & 0 deletions contracts/.changeset/famous-feet-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": patch
---

fix withdraw LINK bug in auto 2.3

Large diffs are not rendered by default.

101 changes: 64 additions & 37 deletions contracts/src/v0.8/automation/dev/test/AutomationRegistry2_3.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ contract SetUp is BaseTest {
"",
""
);

vm.startPrank(OWNER);
registry.addFunds(linkUpkeepID, registry.getMinBalanceForUpkeep(linkUpkeepID));
registry.addFunds(usdUpkeepID, registry.getMinBalanceForUpkeep(usdUpkeepID));
registry.addFunds(nativeUpkeepID, registry.getMinBalanceForUpkeep(nativeUpkeepID));
vm.stopPrank();
}
}

Expand Down Expand Up @@ -124,7 +130,7 @@ contract AddFunds is SetUp {

// it fails when the billing token is not native, but trying to pay with native
function test_RevertsWhen_NativePaymentDoesntMatchBillingToken() external {
vm.expectRevert(abi.encodeWithSelector(Registry.InvalidBillingToken.selector));
vm.expectRevert(abi.encodeWithSelector(Registry.InvalidToken.selector));
registry.addFunds{value: 1}(linkUpkeepID, 0);
}

Expand All @@ -140,32 +146,34 @@ contract AddFunds is SetUp {
}

function test_anyoneCanAddFunds() public {
assertEq(registry.getBalance(linkUpkeepID), 0);
uint256 startAmount = registry.getBalance(linkUpkeepID);
vm.prank(UPKEEP_ADMIN);
registry.addFunds(linkUpkeepID, 1);
assertEq(registry.getBalance(linkUpkeepID), 1);
assertEq(registry.getBalance(linkUpkeepID), startAmount + 1);
vm.prank(STRANGER);
registry.addFunds(linkUpkeepID, 1);
assertEq(registry.getBalance(linkUpkeepID), 2);
assertEq(registry.getBalance(linkUpkeepID), startAmount + 2);
}

function test_movesFundFromCorrectToken() public {
vm.startPrank(UPKEEP_ADMIN);

uint256 startBalanceLINK = linkToken.balanceOf(address(registry));
uint256 startBalanceUSDToken = usdToken.balanceOf(address(registry));
uint256 startLinkUpkeepBalance = registry.getBalance(linkUpkeepID);
uint256 startUSDUpkeepBalance = registry.getBalance(usdUpkeepID);

registry.addFunds(linkUpkeepID, 1);
assertEq(registry.getBalance(linkUpkeepID), 1);
assertEq(registry.getBalance(usdUpkeepID), 0);
assertEq(linkToken.balanceOf(address(registry)), startBalanceLINK + 1);
assertEq(usdToken.balanceOf(address(registry)), startBalanceUSDToken);
assertEq(registry.getBalance(linkUpkeepID), startBalanceLINK + 1);
assertEq(registry.getBalance(usdUpkeepID), startBalanceUSDToken);
assertEq(linkToken.balanceOf(address(registry)), startLinkUpkeepBalance + 1);
assertEq(usdToken.balanceOf(address(registry)), startUSDUpkeepBalance);

registry.addFunds(usdUpkeepID, 2);
assertEq(registry.getBalance(linkUpkeepID), 1);
assertEq(registry.getBalance(usdUpkeepID), 2);
assertEq(linkToken.balanceOf(address(registry)), startBalanceLINK + 1);
assertEq(usdToken.balanceOf(address(registry)), startBalanceUSDToken + 2);
assertEq(registry.getBalance(linkUpkeepID), startBalanceLINK + 1);
assertEq(registry.getBalance(usdUpkeepID), startBalanceUSDToken + 2);
assertEq(linkToken.balanceOf(address(registry)), startLinkUpkeepBalance + 1);
assertEq(usdToken.balanceOf(address(registry)), startUSDUpkeepBalance + 2);
}

function test_emitsAnEvent() public {
Expand All @@ -177,78 +185,97 @@ contract AddFunds is SetUp {
}

contract Withdraw is SetUp {
address internal aMockAddress = address(0x1111111111111111111111111111111111111113);
address internal aMockAddress = randomAddress();

function testLinkAvailableForPaymentReturnsLinkBalance() public {
uint256 startBalance = linkToken.balanceOf(address(registry));
int256 startLinkAvailable = registry.linkAvailableForPayment();

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

//check there's a balance
assertGt(linkToken.balanceOf(address(registry)), 0);
assertEq(linkToken.balanceOf(address(registry)), startBalance + 1e10);

//check the link available for payment is the link balance
assertEq(uint256(registry.linkAvailableForPayment()), linkToken.balanceOf(address(registry)));
//check the link available has increased by the same amount
assertEq(uint256(registry.linkAvailableForPayment()), uint256(startLinkAvailable) + 1e10);
}

function testWithdrawLinkFeesRevertsBecauseOnlyFinanceAdminAllowed() public {
function testWithdrawLinkRevertsBecauseOnlyFinanceAdminAllowed() public {
vm.expectRevert(abi.encodeWithSelector(Registry.OnlyFinanceAdmin.selector));
registry.withdrawLinkFees(aMockAddress, 1);
registry.withdrawLink(aMockAddress, 1);
}

function testWithdrawLinkFeesRevertsBecauseOfInsufficientBalance() public {
function testWithdrawLinkRevertsBecauseOfInsufficientBalance() public {
vm.startPrank(FINANCE_ADMIN);

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

vm.stopPrank();
}

function testWithdrawLinkFeesRevertsBecauseOfInvalidRecipient() public {
function testWithdrawLinkRevertsBecauseOfInvalidRecipient() public {
vm.startPrank(FINANCE_ADMIN);

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

vm.stopPrank();
}

function testWithdrawLinkFeeSuccess() public {
function testWithdrawLinkSuccess() public {
//simulate a deposit of link to the liquidity pool
_mintLink(address(registry), 1e10);

//check there's a balance
assertGt(linkToken.balanceOf(address(registry)), 0);
uint256 startBalance = linkToken.balanceOf(address(registry));

vm.startPrank(FINANCE_ADMIN);

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

vm.stopPrank();

assertEq(linkToken.balanceOf(address(aMockAddress)), 1);
assertEq(linkToken.balanceOf(address(registry)), 1e10 - 1);
assertEq(linkToken.balanceOf(address(registry)), startBalance - 1);
}

function test_WithdrawERC20Fees_RespectsReserveAmount() public {
assertEq(registry.getBalance(usdUpkeepID), registry.getReserveAmount(address(usdToken)));
vm.startPrank(FINANCE_ADMIN);
vm.expectRevert(abi.encodeWithSelector(Registry.InsufficientBalance.selector, 0, 1));
registry.withdrawERC20Fees(address(usdToken), FINANCE_ADMIN, 1);
}

function test_WithdrawERC20Fees_RevertsWhenAttemptingToWithdrawLINK() public {
_mintLink(address(registry), 1e10);
vm.startPrank(FINANCE_ADMIN);
vm.expectRevert(Registry.InvalidToken.selector);
registry.withdrawERC20Fees(address(linkToken), FINANCE_ADMIN, 1); // should revert
registry.withdrawLink(FINANCE_ADMIN, 1); // but using link withdraw functions succeeds
}

function testWithdrawERC20FeeSuccess() public {
// simulate a deposit of ERC20 to the liquidity pool
// deposit excess USDToken to the registry (this goes to the "finance withdrawable" pool be default)
uint256 startReserveAmount = registry.getReserveAmount(address(usdToken));
uint256 startAmount = usdToken.balanceOf(address(registry));
_mintERC20(address(registry), 1e10);

// check there's a balance
assertGt(usdToken.balanceOf(address(registry)), 0);
// depositing shouldn't change reserve amount
assertEq(registry.getReserveAmount(address(usdToken)), startReserveAmount);

vm.startPrank(FINANCE_ADMIN);

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

vm.stopPrank();

assertEq(usdToken.balanceOf(address(aMockAddress)), 1);
assertEq(usdToken.balanceOf(address(registry)), 1e10 - 1);
assertEq(usdToken.balanceOf(address(registry)), startAmount + 1e10 - 1);
assertEq(registry.getReserveAmount(address(usdToken)), startReserveAmount);
}
}

Expand Down Expand Up @@ -530,7 +557,7 @@ contract SetConfig is SetUp {
);
}

function testSetConfigRevertDueToInvalidBillingToken() public {
function testSetConfigRevertDueToInvalidToken() public {
address[] memory billingTokens = new address[](1);
billingTokens[0] = address(linkToken);

Expand All @@ -547,7 +574,7 @@ contract SetConfig is SetUp {
// deploy registry with OFF_CHAIN payout mode
registry = deployRegistry(AutoBase.PayoutMode.OFF_CHAIN);

vm.expectRevert(abi.encodeWithSelector(Registry.InvalidBillingToken.selector));
vm.expectRevert(abi.encodeWithSelector(Registry.InvalidToken.selector));
registry.setConfigTypeSafe(
SIGNERS,
TRANSMITTERS,
Expand Down Expand Up @@ -792,7 +819,7 @@ contract RegisterUpkeep is SetUp {
}

function test_RevertsWhen_TheBillingTokenIsNotConfigured() public {
vm.expectRevert(Registry.InvalidBillingToken.selector);
vm.expectRevert(Registry.InvalidToken.selector);
registry.registerUpkeep(
address(TARGET1),
config.maxPerformGas,
Expand Down Expand Up @@ -871,7 +898,7 @@ contract OnTokenTransfer is SetUp {

function test_RevertsWhen_TheUpkeepDoesNotUseLINKAsItsBillingToken() public {
vm.startPrank(address(linkToken));
vm.expectRevert(Registry.InvalidBillingToken.selector);
vm.expectRevert(Registry.InvalidToken.selector);
registry.onTokenTransfer(UPKEEP_ADMIN, 100, abi.encode(usdUpkeepID));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ contract AutomationRegistry2_3 is AutomationRegistryBase2_3, OCR2Abstract, Chain
if (data.length != 32) revert InvalidDataLength();
uint256 id = abi.decode(data, (uint256));
if (s_upkeep[id].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
if (address(s_upkeep[id].billingToken) != address(i_link)) revert InvalidBillingToken();
if (address(s_upkeep[id].billingToken) != address(i_link)) revert InvalidToken();
s_upkeep[id].balance = s_upkeep[id].balance + uint96(amount);
s_reserveAmounts[IERC20(address(i_link))] = s_reserveAmounts[IERC20(address(i_link))] + amount;
emit FundsAdded(id, sender, uint96(amount));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
error IncorrectNumberOfSignatures();
error IncorrectNumberOfSigners();
error IndexOutOfRange();
error InsufficientBalance(int256 available, uint256 requested);
error InvalidBillingToken();
error InsufficientBalance(uint256 available, uint256 requested);
error InvalidDataLength();
error InvalidFeed();
error InvalidTrigger();
error InvalidPayee();
error InvalidRecipient();
error InvalidReport();
error InvalidSigner();
error InvalidToken();
error InvalidTransmitter();
error InvalidTriggerType();
error MigrationNotPermitted();
Expand Down Expand Up @@ -483,7 +483,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);
event FeesWithdrawn(address indexed assetAddress, address indexed recipient, uint256 amount);

/**
* @param link address of the LINK Token
Expand Down Expand Up @@ -543,7 +543,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
if (upkeep.performGas < PERFORM_GAS_MIN || upkeep.performGas > s_storage.maxPerformGas)
revert GasLimitOutsideRange();
if (address(s_upkeep[id].forwarder) != address(0)) revert UpkeepAlreadyExists();
if (address(s_billingConfigs[upkeep.billingToken].priceFeed) == address(0)) revert InvalidBillingToken();
if (address(s_billingConfigs[upkeep.billingToken].priceFeed) == address(0)) revert InvalidToken();
s_upkeep[id] = upkeep;
s_upkeepAdmin[id] = admin;
s_checkData[id] = checkData;
Expand Down Expand Up @@ -1069,7 +1069,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {

// if LINK is a billing option, payout mode must be ON_CHAIN
if (address(token) == address(i_link) && mode == PayoutMode.OFF_CHAIN) {
revert InvalidBillingToken();
revert InvalidToken();
}
if (address(token) == ZERO_ADDRESS || address(config.priceFeed) == ZERO_ADDRESS) {
revert ZeroAddressNotAllowed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ contract AutomationRegistryLogicB2_3 is AutomationRegistryBase2_3, Chainable {

if (msg.value != 0) {
if (upkeep.billingToken != IERC20(i_wrappedNativeToken)) {
revert InvalidBillingToken();
revert InvalidToken();
}
amount = SafeCast.toUint96(msg.value);
}
Expand Down Expand Up @@ -205,34 +205,51 @@ contract AutomationRegistryLogicB2_3 is AutomationRegistryBase2_3, Chainable {
}

/**
* @notice LINK available to withdraw by the finance team
* @notice returns the size of the LINK liquidity pool
# @dev LINK max supply < 2^96, so casting to int256 is safe
*/
function linkAvailableForPayment() public view returns (int256) {
return int256(i_link.balanceOf(address(this))) - int256(s_reserveAmounts[IERC20(address(i_link))]);
}

function withdrawLinkFees(address to, uint256 amount) external {
/**
* @notice withdraws excess LINK from the liquidity pool
* @param to the address to send the fees to
* @param amount the amount to withdraw
*/
function withdrawLink(address to, uint256 amount) external {
_onlyFinanceAdminAllowed();
if (to == ZERO_ADDRESS) revert InvalidRecipient();

int256 available = linkAvailableForPayment();
if (available < 0 || amount > uint256(available)) revert InsufficientBalance(available, amount);
if (available < 0) {
revert InsufficientBalance(0, amount);
} else if (amount > uint256(available)) {
revert InsufficientBalance(uint256(available), amount);
}

bool transferStatus = i_link.transfer(to, amount);
if (!transferStatus) {
revert TransferFailed();
}
emit FeesWithdrawn(to, address(i_link), amount);
emit FeesWithdrawn(address(i_link), to, amount);
}

function withdrawERC20Fees(address assetAddress, address to, uint256 amount) external {
/**
* @notice withdraws non-LINK fees earned by the contract
* @param asset the asset to withdraw
* @param to the address to send the fees to
* @param amount the amount to withdraw
*/
function withdrawERC20Fees(IERC20 asset, address to, uint256 amount) external {
_onlyFinanceAdminAllowed();
if (to == ZERO_ADDRESS) revert InvalidRecipient();
if (address(asset) == address(i_link)) revert InvalidToken();
uint256 available = asset.balanceOf(address(this)) - s_reserveAmounts[asset];
if (amount > available) revert InsufficientBalance(available, amount);

IERC20(assetAddress).safeTransfer(to, amount);

emit FeesWithdrawn(to, assetAddress, amount);
asset.safeTransfer(to, amount);
emit FeesWithdrawn(address(asset), to, amount);
}

/**
Expand Down
Loading

0 comments on commit a6d385c

Please sign in to comment.