From 80f2cb16f2b62783132607d39f0fd4b39957af3e Mon Sep 17 00:00:00 2001 From: Nick Garfield Date: Wed, 31 May 2023 01:48:28 +0000 Subject: [PATCH 1/2] Gasless program demo --- Cargo.lock | 7 +++ programs/gasless/Cargo.toml | 25 ++++++++++ programs/gasless/README.md | 1 + programs/gasless/src/lib.rs | 95 +++++++++++++++++++++++++++++++++++++ rust-toolchain.toml | 2 +- 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 programs/gasless/Cargo.toml create mode 100644 programs/gasless/README.md create mode 100644 programs/gasless/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 657fa0005..39052d001 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1137,6 +1137,13 @@ dependencies = [ "once_cell", ] +[[package]] +name = "clockwork-gasless-program" +version = "2.0.17" +dependencies = [ + "anchor-lang", +] + [[package]] name = "clockwork-network-program" version = "2.0.17" diff --git a/programs/gasless/Cargo.toml b/programs/gasless/Cargo.toml new file mode 100644 index 000000000..571ccbf29 --- /dev/null +++ b/programs/gasless/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "clockwork-gasless-program" +version = "2.0.17" +description = "Clockwork gasless program" +edition = "2021" +license = "AGPL-3.0-or-later" +homepage = "https://clockwork.xyz" +repository = "https://github.com/clockwork-xyz/clockwork" +documentation = "https://docs.clockwork.xyz" +readme = "./README.md" +keywords = ["solana"] + +[lib] +crate-type = ["cdylib", "lib"] +name = "clockwork_gasless_program" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { features = ["init-if-needed"], version = "0.27.0" } diff --git a/programs/gasless/README.md b/programs/gasless/README.md new file mode 100644 index 000000000..1300bc25a --- /dev/null +++ b/programs/gasless/README.md @@ -0,0 +1 @@ +# Clockwork Http diff --git a/programs/gasless/src/lib.rs b/programs/gasless/src/lib.rs new file mode 100644 index 000000000..59c2577af --- /dev/null +++ b/programs/gasless/src/lib.rs @@ -0,0 +1,95 @@ +use std::mem::size_of; + +use anchor_lang::{prelude::*, solana_program::system_program}; + +declare_id!("E7p5KFo8kKCDm6BUnWtnVFkQSYh6ZA6xaGAuvpv8NXTa"); + +#[program] +pub mod gasless_program { + pub use super::*; + + pub fn payer_create<'info>( + ctx: Context, + authorized_spenders: Vec, + amount: u64, + ) -> Result<()> { + let authority = &ctx.accounts.authority; + let payer = &mut ctx.accounts.payer; + let system_program = &ctx.accounts.system_program; + + payer.bump = *ctx.bumps.get("payer").unwrap(); + payer.authority = authority.key(); + payer.authorized_spenders = authorized_spenders; + + anchor_lang::system_program::transfer( + CpiContext::new( + system_program.to_account_info(), + anchor_lang::system_program::Transfer { + from: authority.to_account_info(), + to: payer.to_account_info(), + }, + ), + amount, + )?; + + Ok(()) + } + + pub fn spend<'info>(_ctx: Context) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts)] +pub struct PayerCreate<'info> { + #[account(mut)] + pub authority: Signer<'info>, + + #[account( + init, + seeds = [ + SEED_PAYER, + authority.key().as_ref(), + ], + bump, + space = 8 + size_of::(), + payer = authority + )] + pub payer: Account<'info, Payer>, + + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Spend<'info> { + #[account()] + pub spender: Signer<'info>, + + #[account( + mut, + seeds = [ + SEED_PAYER, + payer.authority.key().as_ref(), + ], + bump = payer.bump, + constraint = payer.authorized_spenders.contains(&spender.key()) @ GaslessError::UnauthorizedSpender, + )] + pub payer: Account<'info, Payer>, +} + +pub const SEED_PAYER: &[u8] = b"payer"; + +#[account] +#[derive(Debug)] +pub struct Payer { + pub authority: Pubkey, + pub authorized_spenders: Vec, + pub bump: u8, +} + +#[error_code] +pub enum GaslessError { + #[msg("This signer is not authorized to spend from this PDA")] + UnauthorizedSpender, +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f54e3b8be..184939d3f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.60.0" +channel = "1.65.0" components = [ "rustfmt", "rust-analyzer" ] profile = "minimal" targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"] From 31965d31232535a4ad6282d6a81ed68dbbff75ad Mon Sep 17 00:00:00 2001 From: Nick Garfield Date: Wed, 31 May 2023 13:37:33 +0000 Subject: [PATCH 2/2] Gasless demos --- programs/gasless/Cargo.toml | 1 + programs/gasless/src/lib.rs | 88 ++++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/programs/gasless/Cargo.toml b/programs/gasless/Cargo.toml index 571ccbf29..2d5bb6ad4 100644 --- a/programs/gasless/Cargo.toml +++ b/programs/gasless/Cargo.toml @@ -23,3 +23,4 @@ default = [] [dependencies] anchor-lang = { features = ["init-if-needed"], version = "0.27.0" } +clockwork-network-program = { path = "../network", features = ["cpi"], version = "=2.0.17" } diff --git a/programs/gasless/src/lib.rs b/programs/gasless/src/lib.rs index 59c2577af..907bc62ca 100644 --- a/programs/gasless/src/lib.rs +++ b/programs/gasless/src/lib.rs @@ -1,26 +1,29 @@ use std::mem::size_of; use anchor_lang::{prelude::*, solana_program::system_program}; +use clockwork_network_program::state::{Fee, Pool, Worker, WorkerAccount}; -declare_id!("E7p5KFo8kKCDm6BUnWtnVFkQSYh6ZA6xaGAuvpv8NXTa"); +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +/// The ID of the pool workers must be a member of to collect fees. +const POOL_ID: u64 = 1; #[program] -pub mod gasless_program { +pub mod gasless { pub use super::*; - pub fn payer_create<'info>( - ctx: Context, - authorized_spenders: Vec, - amount: u64, - ) -> Result<()> { + pub fn payer_create<'info>(ctx: Context, amount: u64) -> Result<()> { let authority = &ctx.accounts.authority; let payer = &mut ctx.accounts.payer; + let spender = &ctx.accounts.spender; let system_program = &ctx.accounts.system_program; + // Initialize payer account. payer.bump = *ctx.bumps.get("payer").unwrap(); payer.authority = authority.key(); - payer.authorized_spenders = authorized_spenders; + payer.spenders = vec![spender.key()]; + // Fund the payer account. anchor_lang::system_program::transfer( CpiContext::new( system_program.to_account_info(), @@ -35,12 +38,44 @@ pub mod gasless_program { Ok(()) } + pub fn add_spender<'info>(ctx: Context) -> Result<()> { + let authority = &ctx.accounts.authority; + let payer = &mut ctx.accounts.payer; + let spender = &ctx.accounts.spender; + let system_program = &ctx.accounts.system_program; + + // Add authorized spender. + payer.spenders.push(spender.key()); + + // Realloc account size. + let new_data_len = payer.to_account_info().data_len().checked_add(32).unwrap(); + payer.to_account_info().realloc(new_data_len, false)?; + + // Pay min rent requirement. + let rent_requirement = Rent::get().unwrap().minimum_balance(new_data_len); + if rent_requirement.gt(&payer.to_account_info().lamports()) { + anchor_lang::system_program::transfer( + CpiContext::new( + system_program.to_account_info(), + anchor_lang::system_program::Transfer { + from: authority.to_account_info(), + to: payer.to_account_info(), + }, + ), + rent_requirement.saturating_sub(payer.to_account_info().lamports()), + )?; + } + + Ok(()) + } + pub fn spend<'info>(_ctx: Context) -> Result<()> { Ok(()) } } #[derive(Accounts)] +#[instruction(amount: u64)] pub struct PayerCreate<'info> { #[account(mut)] pub authority: Signer<'info>, @@ -52,20 +87,51 @@ pub struct PayerCreate<'info> { authority.key().as_ref(), ], bump, - space = 8 + size_of::(), + space = 8 + size_of::() + 32, payer = authority )] pub payer: Account<'info, Payer>, + #[account()] + pub spender: SystemAccount<'info>, + + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct AddSpender<'info> { + #[account(mut)] + pub authority: Signer<'info>, + + #[account()] + pub spender: SystemAccount<'info>, + + #[account( + mut, + seeds = [ + SEED_PAYER, + authority.key().as_ref(), + ], + bump = payer.bump, + )] + pub payer: Account<'info, Payer>, + #[account(address = system_program::ID)] pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct Spend<'info> { + #[account(mut)] + pub fee: Account<'info, Fee>, + #[account()] pub spender: Signer<'info>, + #[account(mut)] + pub signatory: Signer<'info>, + #[account( mut, seeds = [ @@ -73,7 +139,7 @@ pub struct Spend<'info> { payer.authority.key().as_ref(), ], bump = payer.bump, - constraint = payer.authorized_spenders.contains(&spender.key()) @ GaslessError::UnauthorizedSpender, + constraint = payer.spenders.contains(&spender.key()) @ GaslessError::UnauthorizedSpender, )] pub payer: Account<'info, Payer>, } @@ -84,7 +150,7 @@ pub const SEED_PAYER: &[u8] = b"payer"; #[derive(Debug)] pub struct Payer { pub authority: Pubkey, - pub authorized_spenders: Vec, + pub spenders: Vec, pub bump: u8, }