Skip to content

Commit 98a02e3

Browse files
authored
[fortuna] Withdraw fees (#1610)
* withdraw fees command * hmmmm * rg * withdraw fees * it builds * add fee manager to config * withdrawal cli fixes * cleanup * pr comments * cargo bump * log
1 parent 1b8fee9 commit 98a02e3

File tree

11 files changed

+290
-14
lines changed

11 files changed

+290
-14
lines changed

apps/fortuna/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/fortuna/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fortuna"
3-
version = "6.2.3"
3+
version = "6.3.0"
44
edition = "2021"
55

66
[dependencies]

apps/fortuna/config.sample.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ chains:
66
# Keeper configuration for the chain
77
reveal_delay_blocks: 0
88
gas_limit: 500000
9+
min_keeper_balance: 100000000000000000
910

1011
# Provider configuration
12+
# How much to charge in fees
1113
fee: 1500000000000000
14+
1215
# Historical commitments -- delete this block for local development purposes
1316
commitments:
1417
# prettier-ignore
@@ -34,6 +37,10 @@ provider:
3437
value: abcd
3538
# For production, you can store the private key in a file.
3639
# file: secret.txt
40+
41+
# Set this to the address of your keeper wallet if you would like the keeper wallet to
42+
# be able to withdraw fees from the contract.
43+
fee_manager: 0xADDRESS
3744
keeper:
3845
# An ethereum wallet address and private key for running the keeper service.
3946
# This does not have to be the same key as the provider's key above.

apps/fortuna/src/chain/ethereum.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ impl<M> LegacyTxMiddleware<M> {
104104
}
105105
}
106106

107-
108107
#[derive(Error, Debug)]
109108
pub enum LegacyTxMiddlewareError<M: Middleware> {
110109
#[error("{0}")]
@@ -167,6 +166,16 @@ impl<M: Middleware> Middleware for LegacyTxMiddleware<M> {
167166
}
168167

169168
impl<T: JsonRpcClient + 'static + Clone> SignablePythContractInner<T> {
169+
/// Get the wallet that signs transactions sent to this contract.
170+
pub fn wallet(&self) -> LocalWallet {
171+
self.client().inner().inner().inner().signer().clone()
172+
}
173+
174+
/// Get the underlying provider that communicates with the blockchain.
175+
pub fn provider(&self) -> Provider<T> {
176+
self.client().inner().inner().inner().provider().clone()
177+
}
178+
170179
/// Submit a request for a random number to the contract.
171180
///
172181
/// This method is a version of the autogenned `request` method that parses the emitted logs

apps/fortuna/src/command.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod register_provider;
55
mod request_randomness;
66
mod run;
77
mod setup_provider;
8+
mod withdraw_fees;
89

910
pub use {
1011
generate::generate,
@@ -14,4 +15,5 @@ pub use {
1415
request_randomness::request_randomness,
1516
run::run,
1617
setup_provider::setup_provider,
18+
withdraw_fees::withdraw_fees,
1719
};

apps/fortuna/src/command/setup_provider.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ use {
3232
LocalWallet,
3333
Signer,
3434
},
35-
types::Bytes,
35+
types::{
36+
Address,
37+
Bytes,
38+
},
3639
},
3740
std::sync::Arc,
3841
tracing::Instrument,
@@ -66,7 +69,6 @@ async fn setup_chain_provider(
6669
"Please specify a provider private key in the config file."
6770
))?;
6871
let provider_address = private_key.clone().parse::<LocalWallet>()?.address();
69-
let provider_fee = chain_config.fee;
7072
// Initialize a Provider to interface with the EVM contract.
7173
let contract = Arc::new(SignablePythContract::from_config(&chain_config, &private_key).await?);
7274

@@ -130,15 +132,28 @@ async fn setup_chain_provider(
130132
.await
131133
.map_err(|e| anyhow!("Chain: {} - Failed to register provider: {}", &chain_id, e))?;
132134
tracing::info!("Registered");
133-
} else {
134-
sync_fee(&contract, &provider_info, provider_fee)
135-
.in_current_span()
136-
.await?;
137-
let uri = get_register_uri(&provider_config.uri, &chain_id)?;
138-
sync_uri(&contract, &provider_info, uri)
139-
.in_current_span()
140-
.await?;
141135
}
136+
137+
138+
let provider_info = contract.get_provider_info(provider_address).call().await?;
139+
140+
sync_fee(&contract, &provider_info, chain_config.fee)
141+
.in_current_span()
142+
.await?;
143+
144+
let uri = get_register_uri(&provider_config.uri, &chain_id)?;
145+
sync_uri(&contract, &provider_info, uri)
146+
.in_current_span()
147+
.await?;
148+
149+
sync_fee_manager(
150+
&contract,
151+
&provider_info,
152+
provider_config.fee_manager.unwrap_or(Address::zero()),
153+
)
154+
.in_current_span()
155+
.await?;
156+
142157
Ok(())
143158
}
144159

@@ -180,3 +195,17 @@ async fn sync_fee(
180195
}
181196
Ok(())
182197
}
198+
199+
async fn sync_fee_manager(
200+
contract: &Arc<SignablePythContract>,
201+
provider_info: &ProviderInfo,
202+
fee_manager: Address,
203+
) -> Result<()> {
204+
if provider_info.fee_manager != fee_manager {
205+
tracing::info!("Updating provider fee manager to {:?}", fee_manager);
206+
if let Some(receipt) = contract.set_fee_manager(fee_manager).send().await?.await? {
207+
tracing::info!("Updated provider fee manager: {:?}", receipt);
208+
}
209+
}
210+
Ok(())
211+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use {
2+
crate::{
3+
chain::ethereum::SignablePythContract,
4+
config::{
5+
Config,
6+
WithdrawFeesOptions,
7+
},
8+
},
9+
anyhow::{
10+
anyhow,
11+
Result,
12+
},
13+
ethers::{
14+
signers::Signer,
15+
types::Address,
16+
},
17+
};
18+
19+
20+
pub async fn withdraw_fees(opts: &WithdrawFeesOptions) -> Result<()> {
21+
let config = Config::load(&opts.config.config)?;
22+
23+
let private_key_string = if opts.keeper {
24+
config.keeper.private_key.load()?.ok_or(anyhow!("Please specify a keeper private key in the config or omit the --keeper option to use the provider private key"))?
25+
} else {
26+
config.provider.private_key.load()?.ok_or(anyhow!(
27+
"Please specify a provider private key in the config or provide the --keeper option to use the keeper private key instead."
28+
))?
29+
};
30+
31+
match opts.chain_id.clone() {
32+
Some(chain_id) => {
33+
let chain_config = &config.get_chain_config(&chain_id)?;
34+
let contract =
35+
SignablePythContract::from_config(&chain_config, &private_key_string).await?;
36+
37+
withdraw_fees_for_chain(
38+
contract,
39+
config.provider.address.clone(),
40+
opts.keeper,
41+
opts.retain_balance_wei,
42+
)
43+
.await?;
44+
}
45+
None => {
46+
for (chain_id, chain_config) in config.chains.iter() {
47+
tracing::info!("Withdrawing fees for chain: {}", chain_id);
48+
let contract =
49+
SignablePythContract::from_config(&chain_config, &private_key_string).await?;
50+
51+
withdraw_fees_for_chain(
52+
contract,
53+
config.provider.address.clone(),
54+
opts.keeper,
55+
opts.retain_balance_wei,
56+
)
57+
.await?;
58+
}
59+
}
60+
}
61+
Ok(())
62+
}
63+
64+
pub async fn withdraw_fees_for_chain(
65+
contract: SignablePythContract,
66+
provider_address: Address,
67+
is_fee_manager: bool,
68+
retained_balance: u128,
69+
) -> Result<()> {
70+
tracing::info!("Fetching fees for provider: {:?}", provider_address);
71+
let provider_info = contract.get_provider_info(provider_address).call().await?;
72+
let fees = provider_info.accrued_fees_in_wei;
73+
tracing::info!("Accrued fees: {} wei", fees);
74+
75+
let withdrawal_amount_wei = fees.saturating_sub(retained_balance);
76+
if withdrawal_amount_wei > 0 {
77+
tracing::info!(
78+
"Withdrawing {} wei to {}...",
79+
withdrawal_amount_wei,
80+
contract.wallet().address()
81+
);
82+
83+
let call = match is_fee_manager {
84+
true => contract.withdraw_as_fee_manager(provider_address, withdrawal_amount_wei),
85+
false => contract.withdraw(withdrawal_amount_wei),
86+
};
87+
let tx_result = call.send().await?.await?;
88+
89+
match &tx_result {
90+
Some(receipt) => {
91+
tracing::info!("Withdrawal transaction hash {:?}", receipt.transaction_hash);
92+
}
93+
None => {
94+
tracing::warn!("No transaction receipt. Unclear what happened to the transaction");
95+
}
96+
}
97+
}
98+
99+
Ok(())
100+
}

apps/fortuna/src/config.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub use {
3232
request_randomness::RequestRandomnessOptions,
3333
run::RunOptions,
3434
setup_provider::SetupProviderOptions,
35+
withdraw_fees::WithdrawFeesOptions,
3536
};
3637

3738
mod generate;
@@ -41,6 +42,7 @@ mod register_provider;
4142
mod request_randomness;
4243
mod run;
4344
mod setup_provider;
45+
mod withdraw_fees;
4446

4547
const DEFAULT_RPC_ADDR: &str = "127.0.0.1:34000";
4648
const DEFAULT_HTTP_ADDR: &str = "http://127.0.0.1:34000";
@@ -73,6 +75,9 @@ pub enum Options {
7375

7476
/// Get the status of a pending request for a random number.
7577
GetRequest(GetRequestOptions),
78+
79+
/// Withdraw any of the provider's accumulated fees from the contract.
80+
WithdrawFees(WithdrawFeesOptions),
7681
}
7782

7883
#[derive(Args, Clone, Debug)]
@@ -140,6 +145,12 @@ pub struct EthereumConfig {
140145
/// The gas limit to use for entropy callback transactions.
141146
pub gas_limit: u64,
142147

148+
/// Minimum wallet balance for the keeper. If the balance falls below this level, the keeper will
149+
/// withdraw fees from the contract to top up. This functionality requires the keeper to be the fee
150+
/// manager for the provider.
151+
#[serde(default)]
152+
pub min_keeper_balance: u128,
153+
143154
/// How much the provider charges for a request on this chain.
144155
#[serde(default)]
145156
pub fee: u128,
@@ -186,6 +197,10 @@ pub struct ProviderConfig {
186197
/// compute per request for less RAM use.
187198
#[serde(default = "default_chain_sample_interval")]
188199
pub chain_sample_interval: u64,
200+
201+
/// The address of the fee manager for the provider. Set this value to the keeper wallet address to
202+
/// enable keeper balance top-ups.
203+
pub fee_manager: Option<Address>,
189204
}
190205

191206
fn default_chain_sample_interval() -> u64 {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use {
2+
crate::{
3+
api::ChainId,
4+
config::ConfigOptions,
5+
},
6+
clap::Args,
7+
};
8+
9+
#[derive(Args, Clone, Debug)]
10+
#[command(next_help_heading = "Withdraw Fees Options")]
11+
#[group(id = "Withdraw Fees")]
12+
pub struct WithdrawFeesOptions {
13+
#[command(flatten)]
14+
pub config: ConfigOptions,
15+
16+
/// Withdraw the fees on this chain, or all chains if not specified.
17+
#[arg(long = "chain-id")]
18+
pub chain_id: Option<ChainId>,
19+
20+
/// If provided, run the command using the keeper wallet. By default, the command uses the provider wallet.
21+
/// If this option is provided, the keeper wallet must be configured and set as the fee manager for the provider.
22+
#[arg(long = "keeper")]
23+
#[arg(default_value = "false")]
24+
pub keeper: bool,
25+
26+
/// If specified, only withdraw fees over the given balance from the contract.
27+
/// If omitted, all accrued fees are withdrawn.
28+
#[arg(long = "retain-balance")]
29+
#[arg(default_value = "0")]
30+
pub retain_balance_wei: u128,
31+
}

0 commit comments

Comments
 (0)