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

fix decimals in corner cases and use round up #13287

Merged
merged 2 commits into from
May 29, 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/plenty-waves-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

roundup #bugfix
5 changes: 5 additions & 0 deletions contracts/.changeset/nasty-tables-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": patch
---

roundup #bugfix
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,104 @@ contract Transmit is SetUp {
"native reserve amount should have decreased"
);
}

function test_handlesInsufficientBalanceWithUSDToken18() external {
// deploy and configure a registry with ON_CHAIN payout
(Registry registry, ) = deployAndConfigureRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN);

// register an upkeep and add funds
uint256 upkeepID = registry.registerUpkeep(
address(TARGET1),
1000000,
UPKEEP_ADMIN,
0,
address(usdToken18),
"",
"",
""
);
_mintERC20_18Decimals(UPKEEP_ADMIN, 1e20);
vm.startPrank(UPKEEP_ADMIN);
usdToken18.approve(address(registry), 1e20);
registry.addFunds(upkeepID, 1); // smaller than gasCharge
uint256 balance = registry.getBalance(upkeepID);

// manually create a transmit
vm.recordLogs();
_transmit(upkeepID, registry);
Vm.Log[] memory entries = vm.getRecordedLogs();

assertEq(entries.length, 3);
Vm.Log memory l1 = entries[1];
assertEq(
l1.topics[0],
keccak256("UpkeepCharged(uint256,(uint96,uint96,uint96,uint96,address,uint96,uint96,uint96))")
);
(
uint96 gasChargeInBillingToken,
uint96 premiumInBillingToken,
uint96 gasReimbursementInJuels,
uint96 premiumInJuels,
address billingToken,
uint96 linkUSD,
uint96 nativeUSD,
uint96 billingUSD
) = abi.decode(l1.data, (uint96, uint96, uint96, uint96, address, uint96, uint96, uint96));

assertEq(gasChargeInBillingToken, balance);
assertEq(gasReimbursementInJuels, (balance * billingUSD) / linkUSD);
assertEq(premiumInJuels, 0);
assertEq(premiumInBillingToken, 0);
}

function test_handlesInsufficientBalanceWithUSDToken6() external {
// deploy and configure a registry with ON_CHAIN payout
(Registry registry, ) = deployAndConfigureRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN);

// register an upkeep and add funds
uint256 upkeepID = registry.registerUpkeep(
address(TARGET1),
1000000,
UPKEEP_ADMIN,
0,
address(usdToken6),
"",
"",
""
);
vm.prank(OWNER);
usdToken6.mint(UPKEEP_ADMIN, 1e20);

vm.startPrank(UPKEEP_ADMIN);
usdToken6.approve(address(registry), 1e20);
registry.addFunds(upkeepID, 100); // this is greater than gasCharge but less than (gasCharge + premium)
uint256 balance = registry.getBalance(upkeepID);

// manually create a transmit
vm.recordLogs();
_transmit(upkeepID, registry);
Vm.Log[] memory entries = vm.getRecordedLogs();

assertEq(entries.length, 3);
Vm.Log memory l1 = entries[1];
assertEq(
l1.topics[0],
keccak256("UpkeepCharged(uint256,(uint96,uint96,uint96,uint96,address,uint96,uint96,uint96))")
);
(
uint96 gasChargeInBillingToken,
uint96 premiumInBillingToken,
uint96 gasReimbursementInJuels,
uint96 premiumInJuels,
address billingToken,
uint96 linkUSD,
uint96 nativeUSD,
uint96 billingUSD
) = abi.decode(l1.data, (uint96, uint96, uint96, uint96, address, uint96, uint96, uint96));

assertEq(premiumInJuels, (balance * billingUSD * 1e12) / linkUSD - gasReimbursementInJuels); // scale to 18 decimals
assertEq(premiumInBillingToken, (premiumInJuels * linkUSD + (billingUSD * 1e12 - 1)) / (billingUSD * 1e12));
}
}

contract MigrateReceive is SetUp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ contract AutomationRegistry2_3 is AutomationRegistryBase2_3, OCR2Abstract, Chain
}
}
}
// record payments
// record payments to NOPs, all in LINK
s_transmitters[msg.sender].balance += transmitVars.totalReimbursement;
s_hotVars.totalPremium += transmitVars.totalPremium;
s_reserveAmounts[IERC20(address(i_link))] += transmitVars.totalReimbursement + transmitVars.totalPremium;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
// Next block of constants are only used in maxPayment estimation during checkUpkeep simulation
// These values are calibrated using hardhat tests which simulate various cases and verify that
// the variables result in accurate estimation
uint256 internal constant REGISTRY_CONDITIONAL_OVERHEAD = 97_700; // Fixed gas overhead for conditional upkeeps
uint256 internal constant REGISTRY_LOG_OVERHEAD = 122_000; // Fixed gas overhead for log upkeeps
uint256 internal constant REGISTRY_CONDITIONAL_OVERHEAD = 98_200; // Fixed gas overhead for conditional upkeeps
uint256 internal constant REGISTRY_LOG_OVERHEAD = 122_500; // Fixed gas overhead for log upkeeps
uint256 internal constant REGISTRY_PER_SIGNER_GAS_OVERHEAD = 5_600; // Value scales with f
uint256 internal constant REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD = 24; // Per perform data byte overhead

Expand All @@ -60,7 +60,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
// to account for gas used in payment processing. These values are calibrated using hardhat tests which simulates various cases and verifies that
// the variables result in accurate estimation
uint256 internal constant ACCOUNTING_FIXED_GAS_OVERHEAD = 51_200; // Fixed overhead per tx
uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 13_200; // Overhead per upkeep performed in batch
uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 14_200; // Overhead per upkeep performed in batch

LinkTokenInterface internal immutable i_link;
AggregatorV3Interface internal immutable i_linkUSDFeed;
Expand Down Expand Up @@ -695,21 +695,24 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
uint256 gasPaymentHexaicosaUSD = (gasWei *
(paymentParams.gasLimit + paymentParams.gasOverhead) +
paymentParams.l1CostWei) * paymentParams.nativeUSD; // gasPaymentHexaicosaUSD has an extra 8 zeros because of decimals on nativeUSD feed
// gasChargeInBillingToken is scaled by the billing token's decimals
// gasChargeInBillingToken is scaled by the billing token's decimals. Round up to ensure a minimum billing token is charged for gas
receipt.gasChargeInBillingToken = SafeCast.toUint96(
(gasPaymentHexaicosaUSD * numeratorScalingFactor) /
((gasPaymentHexaicosaUSD * numeratorScalingFactor) +
(paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor - 1)) /
(paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor)
);
// 18 decimals: 26 decimals / 8 decimals
receipt.gasReimbursementInJuels = SafeCast.toUint96(gasPaymentHexaicosaUSD / paymentParams.linkUSD);

// premium calculation
uint256 flatFeeHexaicosaUSD = uint256(paymentParams.billingTokenParams.flatFeeMilliCents) * 1e21; // 1e13 for milliCents to attoUSD and 1e8 for attoUSD to hexaicosaUSD
uint256 premiumHexaicosaUSD = ((((gasWei * paymentParams.gasLimit) + paymentParams.l1CostWei) *
paymentParams.billingTokenParams.gasFeePPB *
paymentParams.nativeUSD) / 1e9) + flatFeeHexaicosaUSD;
// premium is scaled by the billing token's decimals
// premium is scaled by the billing token's decimals. Round up to ensure at least minimum charge
receipt.premiumInBillingToken = SafeCast.toUint96(
(premiumHexaicosaUSD * numeratorScalingFactor) /
((premiumHexaicosaUSD * numeratorScalingFactor) +
(paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor - 1)) /
(paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor)
);
receipt.premiumInJuels = SafeCast.toUint96(premiumHexaicosaUSD / paymentParams.linkUSD);
Expand Down Expand Up @@ -1021,21 +1024,35 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
// payment is in the token's native decimals
uint96 payment = receipt.gasChargeInBillingToken + receipt.premiumInBillingToken;

// scaling factors to adjust decimals between billing token and LINK
uint256 decimals = paymentParams.billingTokenParams.decimals;
uint256 scalingFactor1 = decimals < 18 ? 10 ** (18 - decimals) : 1;
uint256 scalingFactor2 = decimals > 18 ? 10 ** (decimals - 18) : 1;

// this shouldn't happen, but in rare edge cases, we charge the full balance in case the user
// can't cover the amount owed
if (balance < receipt.gasChargeInBillingToken) {
// if the user can't cover the gas fee, then direct all of the payment to the transmitter and distribute no premium to the DON
payment = balance;
receipt.gasReimbursementInJuels = SafeCast.toUint96(
(balance * paymentParams.billingTokenParams.priceUSD) / paymentParams.linkUSD
(balance * paymentParams.billingTokenParams.priceUSD * scalingFactor1) /
(paymentParams.linkUSD * scalingFactor2)
);
receipt.premiumInJuels = 0;
receipt.premiumInBillingToken = 0;
receipt.gasChargeInBillingToken = balance;
} else if (balance < payment) {
// if the user can cover the gas fee, but not the premium, then reduce the premium
payment = balance;
receipt.premiumInJuels = SafeCast.toUint96(
((balance * paymentParams.billingTokenParams.priceUSD) / paymentParams.linkUSD) -
receipt.gasReimbursementInJuels
((balance * paymentParams.billingTokenParams.priceUSD * scalingFactor1) /
(paymentParams.linkUSD * scalingFactor2)) - receipt.gasReimbursementInJuels
);
// round up
receipt.premiumInBillingToken = SafeCast.toUint96(
((receipt.premiumInJuels * paymentParams.linkUSD * scalingFactor2) +
(paymentParams.billingTokenParams.priceUSD * scalingFactor1 - 1)) /
(paymentParams.billingTokenParams.priceUSD * scalingFactor1)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const emptyBytes = '0x'
const emptyBytes32 =
'0x0000000000000000000000000000000000000000000000000000000000000000'

const transmitGasOverhead = 1_025_000
const transmitGasOverhead = 1_040_000
const checkGasOverhead = 600_000

const stalenessSeconds = BigNumber.from(43820)
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ automation_registrar_wrapper2_3: ../../contracts/solc/v0.8.19/AutomationRegistra
automation_registry_logic_a_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.bin 2f267fb8467a15c587ce4586ac56069f7229344ad3936430d7c7624c0528a171
automation_registry_logic_a_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.bin 73b5cc3ece642abbf6f2a4c9188335b71404f4dd0ad10b761390b6397af6f1c8
automation_registry_logic_b_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.bin a6d33dfbbfb0ff253eb59a51f4f6d6d4c22ea5ec95aae52d25d49a312b37a22f
automation_registry_logic_b_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.bin acee8aca275f0aaeae781a505eacb59414bb685725a8af199c71a01f54739784
automation_registry_logic_b_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.bin fbf6f6cf4e6858855ff5da847c3baa4859dd997cfae51f2fa0651e4fa15b92c9
automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin de60f69878e9b32a291a001c91fc8636544c2cfbd9b507c8c1a4873b602bfb62
automation_registry_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.bin 6bab795655521b2fee8425b52f567b7a3f5d9e63b36b9a31f165af59636201c1
automation_registry_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.bin 10078161924b38cf968ceb65f54078412832ada9abeebcd011ee7291811921c2
automation_utils_2_1: ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.bin 815b17b63f15d26a0274b962eefad98cdee4ec897ead58688bbb8e2470e585f5
automation_utils_2_2: ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.abi ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.bin 8743f6231aaefa3f2a0b2d484258070d506e2d0860690e66890dccc3949edb2e
automation_utils_2_3: ../../contracts/solc/v0.8.19/AutomationUtils2_3/AutomationUtils2_3.abi ../../contracts/solc/v0.8.19/AutomationUtils2_3/AutomationUtils2_3.bin 11e2b481dc9a4d936e3443345d45d2cc571164459d214917b42a8054b295393b
Expand Down
Loading