Skip to content

Commit

Permalink
feat: implement gas back
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewwahid committed May 24, 2023
1 parent 86c169d commit 4d3cd66
Show file tree
Hide file tree
Showing 24 changed files with 963 additions and 47 deletions.
2 changes: 1 addition & 1 deletion lib/controller/wallet_connect_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:candide_mobile_app/models/batch.dart';
import 'package:candide_mobile_app/models/gnosis_transaction.dart';
import 'package:candide_mobile_app/models/paymaster/paymaster_response.dart';
import 'package:candide_mobile_app/screens/home/activity/components/transaction_activity_details_card.dart';
import 'package:candide_mobile_app/screens/home/components/transaction_review_sheet.dart';
import 'package:candide_mobile_app/screens/home/components/transaction/transaction_review_sheet.dart';
import 'package:candide_mobile_app/screens/home/send/components/send_review_leading.dart';
import 'package:candide_mobile_app/screens/home/wallet_connect/components/wc_activity_bundle_status_leading.dart';
import 'package:candide_mobile_app/screens/home/wallet_connect/components/wc_review_leading.dart';
Expand Down
17 changes: 14 additions & 3 deletions lib/models/batch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:candide_mobile_app/controller/signers_controller.dart';
import 'package:candide_mobile_app/models/gas.dart';
import 'package:candide_mobile_app/models/gnosis_transaction.dart';
import 'package:candide_mobile_app/models/paymaster/fee_token.dart';
import 'package:candide_mobile_app/models/paymaster/gas_back_data.dart';
import 'package:candide_mobile_app/models/paymaster/paymaster_response.dart';
import 'package:candide_mobile_app/services/paymaster.dart';
import 'package:candide_mobile_app/utils/constants.dart';
Expand All @@ -25,6 +26,7 @@ class Batch {
Network network;
//
GasEstimate? gasEstimate;
GasBackData? gasBack;
FeeToken? _selectedFeeToken;
late PaymasterResponse _paymasterResponse;
List<GnosisTransaction> transactions = [];
Expand Down Expand Up @@ -61,15 +63,15 @@ class Batch {


Future<void> _adjustFeeCurrencyCosts() async{
for (FeeToken feeToken in _paymasterResponse.tokens){
for (FeeToken feeToken in _paymasterResponse.tokens.reversed){
bool isEther = feeToken.token.symbol == network.nativeCurrency && feeToken.token.address == Constants.addressZeroHex;
UserOperation op = await toUserOperation(
BigInt.from(PersistentData.accountStatus.nonce),
proxyDeployed: PersistentData.accountStatus.proxyDeployed,
skipPaymasterData: true,
feeToken: feeToken,
);
BigInt maxCost = FeeCurrencyUtils.calculateFee(op, feeToken.exchangeRate, isEther);
BigInt maxCost = feeToken.calculateFee(op, network);
if (!isEther){
maxCost = maxCost.scale(1.05); // todo check
}
Expand All @@ -86,6 +88,10 @@ class Batch {
}

Future<void> _addPaymasterToUserOp(UserOperation userOp, int chainId) async {
if (gasBack?.gasBackApplied ?? false){
userOp.paymasterAndData = gasBack!.paymasterAndData;
return;
}
String? paymasterData = await Paymaster.getPaymasterData(userOp, selectedFeeToken!.token.address, chainId);
if (paymasterData == null){ // todo network: handle fetching errors
userOp.paymasterAndData = "0x";
Expand Down Expand Up @@ -169,6 +175,11 @@ class Batch {
userOp.verificationGasLimit = _gasEstimate.verificationGasLimit.scale(2);
userOp.maxFeePerGas = _gasEstimate.maxFeePerGas;
userOp.maxPriorityFeePerGas = _gasEstimate.maxPriorityFeePerGas;
if (gasBack == null){
FeeToken _tempGasToken = feeToken ?? paymasterResponse.tokens.first;
BigInt maxETHCost = _tempGasToken.calculateETHFee(userOp, network);
gasBack = await GasBackData.getGasBackData(account, paymasterResponse.paymasterData.paymaster, network, maxETHCost);
}
if (userOp.initCode != "0x"){
userOp.verificationGasLimit += BigInt.from(350000); // higher than normal for deployment
userOp.callGasLimit += multiSendTransaction?.suggestedGasLimit ?? userOp.callGasLimit; // todo remove when first simulateHandleOp is implemented
Expand All @@ -177,7 +188,7 @@ class Batch {
if (_includesPaymaster(feeToken)){
userOp.verificationGasLimit += BigInt.from(35000);
}
if (_includesPaymaster(feeToken) && !skipPaymasterData){
if ((_includesPaymaster(feeToken) || gasBack!.gasBackApplied) && !skipPaymasterData){
await _addPaymasterToUserOp(userOp, account.chainId);
}
//
Expand Down
10 changes: 7 additions & 3 deletions lib/models/gas.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
class GasEstimate {
BigInt callGasLimit;
BigInt basePreVerificationGas;
BigInt preVerificationGas;
BigInt verificationGasLimit;
BigInt maxPriorityFeePerGas;
BigInt maxFeePerGas;
late BigInt extraPreVerificationGas; //specific for L2s
late BigInt l1GasUsed; //specific for L2s
late BigInt l1BaseFee; //specific for L2s

GasEstimate(
{required this.callGasLimit,
required this.preVerificationGas,
required this.verificationGasLimit,
{required this.callGasLimit, // set by bundler
required this.basePreVerificationGas, // set by bundler
required this.preVerificationGas, // calculated/adjusted for chain
required this.verificationGasLimit, // set by bundler
required this.maxPriorityFeePerGas,
required this.maxFeePerGas,
BigInt? l1GasUsed,
Expand All @@ -22,6 +25,7 @@ class GasEstimate {
GasEstimate copy() {
return GasEstimate(
callGasLimit: callGasLimit,
basePreVerificationGas: basePreVerificationGas,
preVerificationGas: preVerificationGas,
verificationGasLimit: verificationGasLimit,
maxPriorityFeePerGas: maxPriorityFeePerGas,
Expand Down
1 change: 1 addition & 0 deletions lib/models/gas_estimators/l1_gas_estimator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class L1GasEstimator extends GasEstimator {
}else{
gasEstimate = prevEstimate.copy();
}
gasEstimate.preVerificationGas = gasEstimate.basePreVerificationGas;
if (includesPaymaster){
gasEstimate.preVerificationGas += BigInt.from(84); // To accommodate for GnosisTransaction.approveAmount which would be 0 before estimation
gasEstimate.preVerificationGas += BigInt.from(2496); // to accommodate for paymasterAndData (156 bytes * 16)
Expand Down
8 changes: 4 additions & 4 deletions lib/models/gas_estimators/l2_gas_estimator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@ class L2GasEstimator extends GasEstimator {
gasEstimate.l1BaseFee = l1BaseFee;
gasEstimate.l1GasUsed = l1GasUsed;
}

BigInt l1GasUsed = gasEstimate.l1GasUsed;
if (includesPaymaster){
gasEstimate.l1GasUsed += BigInt.from(84); // To accommodate for GnosisTransaction.approveAmount which would be 0 before estimation
gasEstimate.l1GasUsed += BigInt.from(2496); // to accommodate for paymasterAndData (156 bytes * 16)
l1GasUsed += BigInt.from(84); // To accommodate for GnosisTransaction.approveAmount which would be 0 before estimation
l1GasUsed += BigInt.from(2496); // to accommodate for paymasterAndData (156 bytes * 16)
}
BigInt scale = gasEstimate.l1BaseFee ~/ gasEstimate.maxFeePerGas;
if (scale == BigInt.zero){
scale = BigInt.one;
}
gasEstimate.preVerificationGas += gasEstimate.l1GasUsed * (scale);
gasEstimate.preVerificationGas = gasEstimate.basePreVerificationGas + (l1GasUsed * (scale));
//
return gasEstimate;
}
Expand Down
22 changes: 15 additions & 7 deletions lib/models/paymaster/fee_token.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import 'package:candide_mobile_app/config/network.dart';
import 'package:candide_mobile_app/controller/token_info_storage.dart';
import 'package:candide_mobile_app/utils/constants.dart';
import 'package:wallet_dart/wallet/user_operation.dart';

class FeeToken {
TokenInfo token;
BigInt fee;
BigInt paymasterFee;
BigInt exchangeRate;

FeeToken({required this.token, required this.fee, required this.exchangeRate});
}
FeeToken({required this.token, required this.fee, required this.paymasterFee, required this.exchangeRate});

class FeeCurrencyUtils {
static final BigInt costOfPost = BigInt.from(45000); // todo shouldn't be hardcoded

static BigInt calculateFee(UserOperation op, BigInt exchangeRate, bool isEther) {
BigInt operationMaxEthCostUsingPaymaster = op.maxFeePerGas * (costOfPost + op.callGasLimit + (op.verificationGasLimit * BigInt.from(isEther ? 1 : 3)) + op.preVerificationGas);
BigInt tokenToEthPrice = (operationMaxEthCostUsingPaymaster * exchangeRate) ~/ BigInt.from(10).pow(18);
return tokenToEthPrice;
BigInt calculateETHFee(UserOperation op, Network network) {
bool isEther = token.symbol == network.nativeCurrency && token.address == Constants.addressZeroHex;
BigInt operationMaxEthCost = op.maxFeePerGas * (costOfPost + op.callGasLimit + (op.verificationGasLimit * BigInt.from(isEther ? 1 : 3)) + op.preVerificationGas);
return operationMaxEthCost;
}

BigInt calculateFee(UserOperation op, Network network) {
BigInt operationMaxEthCost = calculateETHFee(op, network);
BigInt tokenFee = (operationMaxEthCost * exchangeRate) ~/ BigInt.from(10).pow(18);
tokenFee += paymasterFee;
return tokenFee;
}
}
34 changes: 34 additions & 0 deletions lib/models/paymaster/gas_back_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:candide_mobile_app/config/network.dart';
import 'package:candide_mobile_app/models/paymaster/source/paymaster_contract.g.dart';
import 'package:candide_mobile_app/utils/constants.dart';
import 'package:wallet_dart/wallet/account.dart';
import 'package:web3dart/credentials.dart';

class GasBackData {
EthereumAddress paymaster;
String paymasterAndData;
bool gasBackApplied;

GasBackData({required this.paymaster, required this.paymasterAndData, required this.gasBackApplied});

static Future<GasBackData> getGasBackData(Account account, EthereumAddress _paymaster, Network network, BigInt maxETHCost) async {
PaymasterContract paymasterContract = PaymasterContract(address: _paymaster, client: network.client);
BigInt accountGasBack = await paymasterContract.gasBackBalances(account.address);
bool gasBackApplied = accountGasBack >= maxETHCost;
String paymasterAndData = "0x";
if (gasBackApplied){
paymasterAndData += _paymaster.hexNo0x;
paymasterAndData += Constants.addressZero.hexNo0x; // no token
paymasterAndData += BigInt.from(2).toRadixString(16).padLeft(2, '0'); // paymaster mode = 2 (GAS_BACK)
paymasterAndData += BigInt.from(0).toRadixString(16).padLeft(12, '0'); // validUntil = 0 (48 bits = 6 bytes = 12 hex chars)
paymasterAndData += BigInt.from(0).toRadixString(16).padLeft(64, '0'); // fee = 0 (256 bits = 32 bytes = 64 hex chars)
paymasterAndData += BigInt.from(0).toRadixString(16).padLeft(64, '0'); // exchangeRate = 0
}
return GasBackData(
paymaster: _paymaster,
paymasterAndData: paymasterAndData,
gasBackApplied: gasBackApplied,
);
}

}
Loading

0 comments on commit 4d3cd66

Please sign in to comment.