Skip to content

Commit

Permalink
fix decimals in corner cases and use round up
Browse files Browse the repository at this point in the history
  • Loading branch information
shileiwill committed May 22, 2024
1 parent 5169bee commit 339e007
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 7 deletions.
98 changes: 98 additions & 0 deletions contracts/src/v0.8/automation/dev/test/AutomationRegistry2_3.t.sol
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 @@ -695,9 +695,10 @@ 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)
);
receipt.gasReimbursementInJuels = SafeCast.toUint96(gasPaymentHexaicosaUSD / paymentParams.linkUSD);
Expand All @@ -707,9 +708,10 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner {
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 +1023,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

0 comments on commit 339e007

Please sign in to comment.