Skip to content

Commit

Permalink
percentage premium for vrfv2plus with link discount
Browse files Browse the repository at this point in the history
  • Loading branch information
jinhoonbang committed Dec 20, 2023
1 parent 9603e5e commit 0598cc8
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 71 deletions.
5 changes: 5 additions & 0 deletions contracts/src/v0.8/vrf/dev/SubscriptionAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ abstract contract SubscriptionAPI is ConfirmedOwner, IERC677Receiver, IVRFSubscr
// 6685 + // Positive static costs of argument encoding etc. note that it varies by +/- x*12 for every x bytes of non-zero data in the proof.
// Total: 37,185 gas.
uint32 gasAfterPaymentCalculation;
// Premium percentage for native payment. Value of 15 indicates 15% premium of total gas costs. 0 indicates no premium.
uint8 nativePremiumPercentage;
// Discount percentage for link payment. Cannot exceed nativePremiumPercentage value. If nativePremiumPercentage is 15 and
// linkDiscountPercentage is 5, then the final premium is 10% premium of the total gas costs for link payment.
uint8 linkDiscountPercentage;
}
Config public s_config;

Expand Down
49 changes: 22 additions & 27 deletions contracts/src/v0.8/vrf/dev/VRFCoordinatorV2_5.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
error ProvingKeyAlreadyRegistered(bytes32 keyHash);
error NoSuchProvingKey(bytes32 keyHash);
error InvalidLinkWeiPrice(int256 linkWei);
error LinkDiscountPercentageTooHigh(uint8 linkDiscountPercentage, uint8 nativePremiumPercentage);
error InsufficientGasForConsumer(uint256 have, uint256 want);
error NoCorrespondingRequest();
error IncorrectCommitment();
Expand Down Expand Up @@ -71,22 +72,14 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {

int256 public s_fallbackWeiPerUnitLink;

FeeConfig public s_feeConfig;
struct FeeConfig {
// Flat fee charged per fulfillment in millionths of link
// So fee range is [0, 2^32/10^6].
uint32 fulfillmentFlatFeeLinkPPM;
// Flat fee charged per fulfillment in millionths of native.
// So fee range is [0, 2^32/10^6].
uint32 fulfillmentFlatFeeNativePPM;
}
event ConfigSet(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig feeConfig
uint8 nativePremiumPercentage,
uint8 linkDiscountPercentage
);

constructor(address blockhashStore) SubscriptionAPI() {
Expand Down Expand Up @@ -145,15 +138,17 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
* @param stalenessSeconds if the native/link feed is more stale then this, use the fallback price
* @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
* @param fallbackWeiPerUnitLink fallback native/link price in the case of a stale feed
* @param feeConfig fee configuration
* @param nativePremiumPercentage native premium percentage
* @param linkDiscountPercentage link discount percentage
*/
function setConfig(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig memory feeConfig
uint8 nativePremiumPercentage,
uint8 linkDiscountPercentage
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
Expand All @@ -165,22 +160,27 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
if (fallbackWeiPerUnitLink <= 0) {
revert InvalidLinkWeiPrice(fallbackWeiPerUnitLink);
}
if (linkDiscountPercentage > nativePremiumPercentage) {
revert LinkDiscountPercentageTooHigh(linkDiscountPercentage, nativePremiumPercentage);
}
s_config = Config({
minimumRequestConfirmations: minimumRequestConfirmations,
maxGasLimit: maxGasLimit,
stalenessSeconds: stalenessSeconds,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
reentrancyLock: false
reentrancyLock: false,
nativePremiumPercentage: nativePremiumPercentage,
linkDiscountPercentage: linkDiscountPercentage
});
s_feeConfig = feeConfig;
s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink;
emit ConfigSet(
minimumRequestConfirmations,
maxGasLimit,
stalenessSeconds,
gasAfterPaymentCalculation,
fallbackWeiPerUnitLink,
s_feeConfig
nativePremiumPercentage,
linkDiscountPercentage
);
}

Expand Down Expand Up @@ -463,40 +463,34 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
_calculatePaymentAmountNative(
startGas,
gasAfterPaymentCalculation,
s_feeConfig.fulfillmentFlatFeeNativePPM,
weiPerUnitGas
);
}
return
_calculatePaymentAmountLink(
startGas,
gasAfterPaymentCalculation,
s_feeConfig.fulfillmentFlatFeeLinkPPM,
weiPerUnitGas
);
}

function _calculatePaymentAmountNative(
uint256 startGas,
uint256 gasAfterPaymentCalculation,
uint32 fulfillmentFlatFeePPM,
uint256 weiPerUnitGas
) internal view returns (uint96) {
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data);
// calculate the payment without the premium
uint256 baseFeeWei = weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft());
// calculate the flat fee in wei
uint256 flatFeeWei = 1e12 * uint256(fulfillmentFlatFeePPM);
// return the final fee with the flat fee and l1 cost (if applicable) added
return uint96(baseFeeWei + flatFeeWei + l1CostWei);
return uint96((l1CostWei + baseFeeWei) * (100 + s_config.nativePremiumPercentage) / 100);
}

// Get the amount of gas used for fulfillment
function _calculatePaymentAmountLink(
uint256 startGas,
uint256 gasAfterPaymentCalculation,
uint32 fulfillmentFlatFeeLinkPPM,
uint256 weiPerUnitGas
) internal view returns (uint96) {
int256 weiPerUnitLink;
Expand All @@ -509,11 +503,12 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
// (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels
uint256 paymentNoFee = (1e18 * (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) /
uint256(weiPerUnitLink);
uint256 fee = 1e12 * uint256(fulfillmentFlatFeeLinkPPM);
if (paymentNoFee > (1e27 - fee)) {
Config memory config = s_config;
uint256 payment = (paymentNoFee * (100 + config.nativePremiumPercentage - config.linkDiscountPercentage)) / 100;
if (payment > 1e27) {
revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence.
}
return uint96(paymentNoFee + fee);
return uint96(payment);
}

function _getFeedData() private view returns (int256) {
Expand Down Expand Up @@ -689,8 +684,8 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {

emit MigrationCompleted(newCoordinator, subId);
}

function migrationVersion() public pure returns (uint8 version) {
return 1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig feeConfig
uint8 nativePremiumPercentage,
uint8 linkDiscountPercentage
);

constructor(address blockhashStore) SubscriptionAPI() {
Expand Down Expand Up @@ -136,15 +137,17 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
* @param stalenessSeconds if the native/link feed is more stale then this, use the fallback price
* @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
* @param fallbackWeiPerUnitLink fallback native/link price in the case of a stale feed
* @param feeConfig fee configuration
* @param nativePremiumPercentage native premium percentage
* @param linkDiscountPercentage link discount percentage
*/
function setConfig(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig memory feeConfig
uint8 nativePremiumPercentage,
uint8 linkDiscountPercentage
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
Expand All @@ -161,17 +164,19 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
maxGasLimit: maxGasLimit,
stalenessSeconds: stalenessSeconds,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
reentrancyLock: false
reentrancyLock: false,
nativePremiumPercentage: nativePremiumPercentage,
linkDiscountPercentage: linkDiscountPercentage
});
s_feeConfig = feeConfig;
s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink;
emit ConfigSet(
minimumRequestConfirmations,
maxGasLimit,
stalenessSeconds,
gasAfterPaymentCalculation,
fallbackWeiPerUnitLink,
s_feeConfig
nativePremiumPercentage,
linkDiscountPercentage
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,17 @@ contract VRFCoordinatorV2Plus_Migration is BaseTest {
600,
10_000,
20_000,
VRFCoordinatorV2_5.FeeConfig({fulfillmentFlatFeeLinkPPM: 200, fulfillmentFlatFeeNativePPM: 100})
15, // nativePremiumPercentage
5 // linkDiscountPercentage
);
v1Coordinator_noLink.setConfig(
DEFAULT_REQUEST_CONFIRMATIONS,
DEFAULT_CALLBACK_GAS_LIMIT,
600,
10_000,
20_000,
VRFCoordinatorV2_5.FeeConfig({fulfillmentFlatFeeLinkPPM: 200, fulfillmentFlatFeeNativePPM: 100})
15, // nativePremiumPercentage
5 // linkDiscountPercentage
);
registerProvingKey();
testConsumer.setCoordinator(v1CoordinatorAddr);
Expand Down
46 changes: 22 additions & 24 deletions contracts/test/v0.8/foundry/vrf/VRFV2Plus.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ contract VRFV2Plus is BaseTest {
MockLinkToken s_linkToken;
MockV3Aggregator s_linkNativeFeed;

VRFCoordinatorV2_5.FeeConfig basicFeeConfig =
VRFCoordinatorV2_5.FeeConfig({fulfillmentFlatFeeLinkPPM: 0, fulfillmentFlatFeeNativePPM: 0});

// VRF KeyV2 generated from a node; not sensitive information.
// The secret key used to generate this key is: 10.
bytes vrfUncompressedPublicKey =
Expand Down Expand Up @@ -88,31 +85,32 @@ contract VRFV2Plus is BaseTest {
s_testCoordinator.setLINKAndLINKNativeFeed(address(s_linkToken), address(s_linkNativeFeed));
}

function setConfig(VRFCoordinatorV2_5.FeeConfig memory feeConfig) internal {
function setConfig() internal {
s_testCoordinator.setConfig(
0, // minRequestConfirmations
2_500_000, // maxGasLimit
1, // stalenessSeconds
50_000, // gasAfterPaymentCalculation
50000000000000000, // fallbackWeiPerUnitLink
feeConfig
15, // nativePremiumPercentage
5 // linkDiscountPercentage
);
}

function testSetConfig() public {
// Should setConfig successfully.
setConfig(basicFeeConfig);
setConfig();
(uint16 minConfs, uint32 gasLimit, ) = s_testCoordinator.getRequestConfig();
assertEq(minConfs, 0);
assertEq(gasLimit, 2_500_000);

// Test that setting requestConfirmations above MAX_REQUEST_CONFIRMATIONS reverts.
vm.expectRevert(abi.encodeWithSelector(VRFCoordinatorV2_5.InvalidRequestConfirmations.selector, 500, 500, 200));
s_testCoordinator.setConfig(500, 2_500_000, 1, 50_000, 50000000000000000, basicFeeConfig);
s_testCoordinator.setConfig(500, 2_500_000, 1, 50_000, 50000000000000000, 15, 5);

// Test that setting fallbackWeiPerUnitLink to zero reverts.
vm.expectRevert(abi.encodeWithSelector(VRFCoordinatorV2_5.InvalidLinkWeiPrice.selector, 0));
s_testCoordinator.setConfig(0, 2_500_000, 1, 50_000, 0, basicFeeConfig);
s_testCoordinator.setConfig(0, 2_500_000, 1, 50_000, 0, 15, 5);
}

function testRegisterProvingKey() public {
Expand Down Expand Up @@ -247,7 +245,7 @@ contract VRFV2Plus is BaseTest {
s_testCoordinator.fundSubscriptionWithNative{value: 10 ether}(subId);

// Apply basic configs to contract.
setConfig(basicFeeConfig);
setConfig();
registerProvingKey();

// Request random words.
Expand Down Expand Up @@ -341,19 +339,19 @@ contract VRFV2Plus is BaseTest {
(fulfilled, , ) = s_testConsumer.s_requests(requestId);
assertEq(fulfilled, true);

// The cost of fulfillRandomWords is approximately 100_000 gas.
// The cost of fulfillRandomWords is approximately 70_000 gas.
// gasAfterPaymentCalculation is 50_000.
//
// The cost of the VRF fulfillment charged to the user is:
// baseFeeWei = weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft())
// baseFeeWei = 1 * (50_000 + 100_000)
// baseFeeWei = 150_000
// baseFeeWei = 1 * (50_000 + 70_000)
// baseFeeWei = 120_000
// ...
// billed_fee = baseFeeWei + flatFeeWei + l1CostWei
// billed_fee = baseFeeWei + 0 + 0
// billed_fee = 150_000
// billed_fee = baseFeeWei * (100 + linkPremiumPercentage / 100)
// billed_fee = baseFeeWei * 1.15
// billed_fee = 138_000
(, uint96 nativeBalanceAfter, , , ) = s_testCoordinator.getSubscription(subId);
assertApproxEqAbs(nativeBalanceAfter, nativeBalanceBefore - 120_000, 10_000);
assertApproxEqAbs(nativeBalanceAfter, nativeBalanceBefore - 138_000, 10_000);
}

function testRequestAndFulfillRandomWordsLINK() public {
Expand All @@ -364,7 +362,7 @@ contract VRFV2Plus is BaseTest {
uint256 subId = s_testConsumer.s_subId();

// Apply basic configs to contract.
setConfig(basicFeeConfig);
setConfig();
registerProvingKey();

// Request random words.
Expand Down Expand Up @@ -458,19 +456,19 @@ contract VRFV2Plus is BaseTest {
(fulfilled, , ) = s_testConsumer.s_requests(requestId);
assertEq(fulfilled, true);

// The cost of fulfillRandomWords is approximately 90_000 gas.
// The cost of fulfillRandomWords is approximately 96_000 gas.
// gasAfterPaymentCalculation is 50_000.
//
// The cost of the VRF fulfillment charged to the user is:
// paymentNoFee = (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft() + l1CostWei) / link_native_ratio)
// paymentNoFee = (1 * (50_000 + 90_000 + 0)) / .5
// paymentNoFee = 280_000
// paymentNoFee = (1 * (50_000 + 96_000 + 0)) / .5
// paymentNoFee = 292_000
// ...
// billed_fee = paymentNoFee + fulfillmentFlatFeeLinkPPM
// billed_fee = baseFeeWei + 0
// billed_fee = 280_000
// billed_fee = paymentNoFee * ((100 + nativePremiumPercentage - linkDiscountPercent) / 100)
// billed_fee = paymentNoFee * 1.1
// billed_fee = 321_200
// note: delta is doubled from the native test to account for more variance due to the link/native ratio
(uint96 linkBalanceAfter, , , , ) = s_testCoordinator.getSubscription(subId);
assertApproxEqAbs(linkBalanceAfter, linkBalanceBefore - 280_000, 20_000);
assertApproxEqAbs(linkBalanceAfter, linkBalanceBefore - 321_200, 10_000);
}
}
10 changes: 4 additions & 6 deletions contracts/test/v0.8/foundry/vrf/VRFV2Wrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ contract VRFV2PlusWrapperTest is BaseTest {
VRFV2PlusWrapper s_wrapper;
VRFV2PlusWrapperConsumerExample s_consumer;

VRFCoordinatorV2_5.FeeConfig basicFeeConfig =
VRFCoordinatorV2_5.FeeConfig({fulfillmentFlatFeeLinkPPM: 0, fulfillmentFlatFeeNativePPM: 0});

function setUp() public override {
BaseTest.setUp();

Expand All @@ -46,20 +43,21 @@ contract VRFV2PlusWrapperTest is BaseTest {

// Configure the coordinator.
s_testCoordinator.setLINKAndLINKNativeFeed(address(s_linkToken), address(s_linkNativeFeed));
setConfigCoordinator(basicFeeConfig);
setConfigCoordinator();
setConfigWrapper();

s_testCoordinator.s_config();
}

function setConfigCoordinator(VRFCoordinatorV2_5.FeeConfig memory feeConfig) internal {
function setConfigCoordinator() internal {
s_testCoordinator.setConfig(
0, // minRequestConfirmations
2_500_000, // maxGasLimit
1, // stalenessSeconds
50_000, // gasAfterPaymentCalculation
50000000000000000, // fallbackWeiPerUnitLink
feeConfig
15, // nativePremiumPercentage
5 // linkDiscountPercentage
);
}

Expand Down
Loading

0 comments on commit 0598cc8

Please sign in to comment.