Skip to content

Commit

Permalink
[WIP] Auto 9112 convert registry / registrar to use billing token (#1…
Browse files Browse the repository at this point in the history
…2418)

* refactor registrar to support billing tokens on registration

* fix registry tests

* add tests for billing token on registration path

* make registrar check for valid billing tokens

* use billing token in calculatePaymentAmount()

* regenerate master interface

* change UnsupportedBillingToken to InvalidBillingToken

* update foundry tests

* refactor foundry tests

* use billing token agnostic reserve amounts

* add getReserveAmount()

* update registrar to support min amounts per billing token

* update comments

* remove billing params from OnChainConfig

* use safeCast to uint96 for balances

* make min spend configurable per billing token

* use billing token for funding in registrar

* change premiumWei to premium

* regenerate master interface after rebase

* regenerate wrappers

* add changeset

* run lint fix
  • Loading branch information
RyanRHall authored Mar 18, 2024
1 parent 98108a8 commit 22114fb
Show file tree
Hide file tree
Showing 18 changed files with 1,889 additions and 1,265 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/quick-vans-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": minor
---

introduce native billing support to automation registry v2.3

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {BaseTest} from "./BaseTest.t.sol";
import {IAutomationRegistryMaster2_3} from "../interfaces/v2_3/IAutomationRegistryMaster2_3.sol";
import {AutomationRegistrar2_3} from "../v2_3/AutomationRegistrar2_3.sol";
import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";

// forge test --match-path src/v0.8/automation/dev/test/AutomationRegistrar2_3.t.sol

contract SetUp is BaseTest {
IAutomationRegistryMaster2_3 internal registry;
AutomationRegistrar2_3 internal registrar;

function setUp() public override {
super.setUp();
registry = deployRegistry();
AutomationRegistrar2_3.InitialTriggerConfig[]
memory triggerConfigs = new AutomationRegistrar2_3.InitialTriggerConfig[](2);
triggerConfigs[0] = AutomationRegistrar2_3.InitialTriggerConfig({
triggerType: 0, // condition
autoApproveType: AutomationRegistrar2_3.AutoApproveType.DISABLED,
autoApproveMaxAllowed: 0
});
triggerConfigs[1] = AutomationRegistrar2_3.InitialTriggerConfig({
triggerType: 1, // log
autoApproveType: AutomationRegistrar2_3.AutoApproveType.DISABLED,
autoApproveMaxAllowed: 0
});
IERC20[] memory billingTokens;
uint256[] memory minRegistrationFees;
registrar = new AutomationRegistrar2_3(
address(linkToken),
registry,
triggerConfigs,
billingTokens,
minRegistrationFees
);
}
}

contract OnTokenTransfer is SetUp {}
107 changes: 46 additions & 61 deletions contracts/src/v0.8/automation/dev/test/AutomationRegistry2_3.t.sol
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {AutomationForwarderLogic} from "../../AutomationForwarderLogic.sol";
import {BaseTest} from "./BaseTest.t.sol";
import {AutomationRegistry2_3} from "../v2_3/AutomationRegistry2_3.sol";
import {AutomationRegistryLogicA2_3} from "../v2_3/AutomationRegistryLogicA2_3.sol";
import {AutomationRegistryLogicB2_3} from "../v2_3/AutomationRegistryLogicB2_3.sol";
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 FINANCE_ADMIN_ADDRESS = 0x1111111111111111111111111111111111111114;
address internal constant ZERO_ADDRESS = address(0);
address internal constant UPKEEP_ADMIN = address(uint160(uint256(keccak256("ADMIN"))));
// forge test --match-path src/v0.8/automation/dev/test/AutomationRegistry2_3.t.sol

contract AutomationRegistry2_3_SetUp is BaseTest {
// Signer private keys used for these test
uint256 internal constant PRIVATE0 = 0x7b2e97fe057e6de99d6872a2ef2abf52c9b4469bc848c2465ac3fcd8d336e81d;
uint256 internal constant PRIVATE1 = 0xab56160806b05ef1796789248e1d7f34a6465c5280899159d645218cd216cee6;
Expand All @@ -33,16 +22,9 @@ 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);
super.setUp();

s_valid_transmitters = new address[](4);
for (uint160 i = 0; i < 4; ++i) {
Expand All @@ -58,19 +40,7 @@ contract AutomationRegistry2_3_SetUp is BaseTest {
s_registrars = new address[](1);
s_registrars[0] = 0x3a0eDE26aa188BFE00b9A0C9A431A1a0CA5f7966;

AutomationForwarderLogic forwarderLogic = new AutomationForwarderLogic();
AutomationRegistryLogicB2_3 logicB2_3 = new AutomationRegistryLogicB2_3(
address(link),
LINK_USD_FEED,
NATIVE_USD_FEED,
FAST_GAS_FEED,
address(forwarderLogic),
ZERO_ADDRESS
);
AutomationRegistryLogicA2_3 logicA2_3 = new AutomationRegistryLogicA2_3(logicB2_3);
registryMaster = IAutomationRegistryMaster2_3(
address(new AutomationRegistry2_3(AutomationRegistryLogicB2_3(address(logicA2_3))))
);
registryMaster = deployRegistry();
}
}

Expand Down Expand Up @@ -99,26 +69,23 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
address internal aMockAddress = address(0x1111111111111111111111111111111111111113);

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

function mintERC20(address recipient, uint256 amount) public {
vm.prank(UPKEEP_ADMIN);
vm.prank(OWNER);
//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,
Expand All @@ -131,7 +98,7 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
upkeepPrivilegeManager: 0xD9c855F08A7e460691F41bBDDe6eC310bc0593D8,
chainModule: module,
reorgProtectionEnabled: true,
financeAdmin: FINANCE_ADMIN_ADDRESS
financeAdmin: FINANCE_ADMIN
});
bytes memory offchainConfigBytes = abi.encode(1234, ZERO_ADDRESS);

Expand All @@ -152,10 +119,10 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
mintLink(address(registryMaster), 1e10);

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

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

function testWithdrawLinkFeesRevertsBecauseOnlyFinanceAdminAllowed() public {
Expand All @@ -170,7 +137,7 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
// set config with the finance admin
setConfigForWithdraw();

vm.startPrank(FINANCE_ADMIN_ADDRESS);
vm.startPrank(FINANCE_ADMIN);

// try to withdraw 1 link while there is 0 balance
vm.expectRevert(abi.encodeWithSelector(IAutomationRegistryMaster2_3.InsufficientBalance.selector, 0, 1));
Expand All @@ -183,7 +150,7 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
// set config with the finance admin
setConfigForWithdraw();

vm.startPrank(FINANCE_ADMIN_ADDRESS);
vm.startPrank(FINANCE_ADMIN);

// try to withdraw 1 link while there is 0 balance
vm.expectRevert(abi.encodeWithSelector(IAutomationRegistryMaster2_3.InvalidRecipient.selector));
Expand All @@ -200,17 +167,17 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
mintLink(address(registryMaster), 1e10);

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

vm.startPrank(FINANCE_ADMIN_ADDRESS);
vm.startPrank(FINANCE_ADMIN);

// 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);
assertEq(linkToken.balanceOf(address(aMockAddress)), 1);
assertEq(linkToken.balanceOf(address(registryMaster)), 1e10 - 1);
}

function testWithdrawERC20FeeSuccess() public {
Expand All @@ -223,7 +190,7 @@ contract AutomationRegistry2_3_Withdraw is AutomationRegistry2_3_SetUp {
// check there's a balance
assertGt(mockERC20.balanceOf(address(registryMaster)), 0);

vm.startPrank(FINANCE_ADMIN_ADDRESS);
vm.startPrank(FINANCE_ADMIN);

// try to withdraw 1 link while there is a ton of link available
registryMaster.withdrawERC20Fees(address(mockERC20), aMockAddress, 1);
Expand Down Expand Up @@ -251,12 +218,9 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
address module = address(new ChainModuleBase());
AutomationRegistryBase2_3.OnchainConfig 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,
Expand All @@ -269,7 +233,7 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
upkeepPrivilegeManager: 0xD9c855F08A7e460691F41bBDDe6eC310bc0593D8,
chainModule: module,
reorgProtectionEnabled: true,
financeAdmin: FINANCE_ADMIN_ADDRESS
financeAdmin: FINANCE_ADMIN
});

function testSetConfigSuccess() public {
Expand All @@ -284,7 +248,9 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_000,
flatFeeMicroLink: 20_000,
priceFeed: 0x2222222222222222222222222222222222222222
priceFeed: 0x2222222222222222222222222222222222222222,
fallbackPrice: 2_000_000_000, // $20
minSpend: 100_000
});

bytes memory onchainConfigBytes = abi.encode(cfg);
Expand Down Expand Up @@ -337,6 +303,7 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
assertEq(config.gasFeePPB, 5_000);
assertEq(config.flatFeeMicroLink, 20_000);
assertEq(config.priceFeed, 0x2222222222222222222222222222222222222222);
assertEq(config.minSpend, 100_000);

address[] memory tokens = registryMaster.getBillingTokens();
assertEq(tokens.length, 1);
Expand All @@ -356,12 +323,16 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_001,
flatFeeMicroLink: 20_001,
priceFeed: 0x2222222222222222222222222222222222222221
priceFeed: 0x2222222222222222222222222222222222222221,
fallbackPrice: 100,
minSpend: 100
});
billingConfigs[1] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_002,
flatFeeMicroLink: 20_002,
priceFeed: 0x2222222222222222222222222222222222222222
priceFeed: 0x2222222222222222222222222222222222222222,
fallbackPrice: 200,
minSpend: 200
});

bytes memory onchainConfigBytesWithBilling = abi.encode(cfg, billingTokens, billingConfigs);
Expand Down Expand Up @@ -389,11 +360,15 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
assertEq(config1.gasFeePPB, 5_001);
assertEq(config1.flatFeeMicroLink, 20_001);
assertEq(config1.priceFeed, 0x2222222222222222222222222222222222222221);
assertEq(config1.fallbackPrice, 100);
assertEq(config1.minSpend, 100);

AutomationRegistryBase2_3.BillingConfig memory config2 = registryMaster.getBillingTokenConfig(billingTokenAddress2);
assertEq(config2.gasFeePPB, 5_002);
assertEq(config2.flatFeeMicroLink, 20_002);
assertEq(config2.priceFeed, 0x2222222222222222222222222222222222222222);
assertEq(config2.fallbackPrice, 200);
assertEq(config2.minSpend, 200);

address[] memory tokens = registryMaster.getBillingTokens();
assertEq(tokens.length, 2);
Expand All @@ -412,7 +387,9 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
billingConfigs1[0] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_001,
flatFeeMicroLink: 20_001,
priceFeed: 0x2222222222222222222222222222222222222221
priceFeed: 0x2222222222222222222222222222222222222221,
fallbackPrice: 100,
minSpend: 100
});

bytes memory onchainConfigBytesWithBilling1 = abi.encode(cfg, billingTokens1, billingConfigs1);
Expand All @@ -426,7 +403,9 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
billingConfigs2[0] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_002,
flatFeeMicroLink: 20_002,
priceFeed: 0x2222222222222222222222222222222222222222
priceFeed: 0x2222222222222222222222222222222222222222,
fallbackPrice: 200,
minSpend: 200
});

bytes memory onchainConfigBytesWithBilling2 = abi.encode(cfg, billingTokens2, billingConfigs2);
Expand Down Expand Up @@ -465,6 +444,8 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
assertEq(config2.gasFeePPB, 5_002);
assertEq(config2.flatFeeMicroLink, 20_002);
assertEq(config2.priceFeed, 0x2222222222222222222222222222222222222222);
assertEq(config2.fallbackPrice, 200);
assertEq(config2.minSpend, 200);

address[] memory tokens = registryMaster.getBillingTokens();
assertEq(tokens.length, 1);
Expand All @@ -484,12 +465,16 @@ contract AutomationRegistry2_3_SetConfig is AutomationRegistry2_3_SetUp {
billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_001,
flatFeeMicroLink: 20_001,
priceFeed: 0x2222222222222222222222222222222222222221
priceFeed: 0x2222222222222222222222222222222222222221,
fallbackPrice: 100,
minSpend: 100
});
billingConfigs[1] = AutomationRegistryBase2_3.BillingConfig({
gasFeePPB: 5_002,
flatFeeMicroLink: 20_002,
priceFeed: 0x2222222222222222222222222222222222222222
priceFeed: 0x2222222222222222222222222222222222222222,
fallbackPrice: 200,
minSpend: 200
});

bytes memory onchainConfigBytesWithBilling = abi.encode(cfg, billingTokens, billingConfigs);
Expand Down
Loading

0 comments on commit 22114fb

Please sign in to comment.