From b81e3f5332dcfb05b74adf0bab9fcf0ee62bf44e Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 24 Oct 2023 22:48:59 -0400 Subject: [PATCH 1/4] start the code for a vrgda contract --- src/lib.cairo | 1 + src/vrgda.cairo | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/vrgda.cairo diff --git a/src/lib.cairo b/src/lib.cairo index a7321db..5484297 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -3,6 +3,7 @@ mod governor; mod governance_token; mod timelock; mod call_trait; +mod vrgda; #[cfg(test)] mod tests; diff --git a/src/vrgda.cairo b/src/vrgda.cairo new file mode 100644 index 0000000..9c0bb08 --- /dev/null +++ b/src/vrgda.cairo @@ -0,0 +1,121 @@ +use starknet::{ContractAddress}; + +#[starknet::interface] +trait IVRGDA { + // Buy the token with the ETH transferred to this contract, receiving at least min_amount_out + // The result is transferred to the recipient + fn buy(ref self: TContractState, min_amount_out: u128) -> u128; + + // Refund any payment token that is still held by the contract + fn clear_payment(ref self: TContractState) -> u256; +} + +#[starknet::interface] +trait IERC20 { + fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} + +#[derive(Drop, Copy, Serde, starknet::Store)] +struct SaleConfig { + // When the sales should begin, in epoch seconds + start_time: u64, + // How long the sale will last, in seconds + duration: u64, + // A 64 bit number indicating the number of tokens that should be sold per second + // The token is assumed to have 18 decimals, so this number is not a fractional quantity, i.e. 1 represents 1e-18 tokens per second + sell_rate: u64, +} + +#[starknet::contract] +mod VRGDA { + use super::{SaleConfig, ContractAddress, IVRGDA, IERC20Dispatcher, IERC20DispatcherTrait}; + use starknet::{get_caller_address, get_contract_address, contract_address_const}; + + #[storage] + struct Storage { + // The token that is used to buy the token + payment_token: IERC20Dispatcher, + // The token that gives the user the right to purchase the sold token + option_token: IERC20Dispatcher, + // The token that is sold + sold_token: IERC20Dispatcher, + // the configuration of the sale + sale_config: SaleConfig, + // The address that receives the payment token from the sale of the tokens + benefactor: ContractAddress, + // The number of sold tokens thus far + amount_sold: u128, + } + + #[derive(starknet::Event, Drop)] + struct Buy { + buyer: ContractAddress, + paid: u128, + received: u128, + } + + #[derive(starknet::Event, Drop)] + #[event] + enum Event { + Buy: Buy, + } + + #[constructor] + fn constructor( + ref self: ContractState, + payment_token: IERC20Dispatcher, + option_token: IERC20Dispatcher, + sold_token: IERC20Dispatcher, + sale_config: SaleConfig, + benefactor: ContractAddress + ) { + self.payment_token.write(payment_token); + self.option_token.write(option_token); + self.sold_token.write(sold_token); + self.sale_config.write(sale_config); + self.benefactor.write(benefactor); + } + + #[external(v0)] + impl VRGDAImpl of IVRGDA { + fn buy(ref self: ContractState, min_amount_out: u128) -> u128 { + let pt = self.payment_token.read(); + let ot = self.option_token.read(); + let st = self.sold_token.read(); + let paid: u128 = pt + .balanceOf(get_contract_address()) + .try_into() + .expect('PAID_OVERFLOW'); + + let amount_sold = self.amount_sold.read(); + + // todo: compute the amount received based on current time + let received: u128 = 0; + + assert(received >= min_amount_out, 'PURCHASED'); + + // Account for the newly sold amount before transferring anything + self.amount_sold.write(amount_sold + received); + + assert( + received.into() <= (amount_sold.into() + ot.balanceOf(get_contract_address())), + 'INSUFFICIENT_OPTIONS' + ); + + self.emit(Buy { buyer: get_caller_address(), paid, received }); + + st.transfer(get_caller_address(), received.into()); + + received + } + + fn clear_payment(ref self: ContractState) -> u256 { + let pt = self.payment_token.read(); + let refund = pt.balanceOf(get_contract_address()); + pt.transfer(get_caller_address(), refund); + refund + } + } +} + From 146e1dc8a86aa55b7e9c9cf4c31d82d05585322f Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 24 Oct 2023 22:56:37 -0400 Subject: [PATCH 2/4] improve the style and naming --- src/vrgda.cairo | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vrgda.cairo b/src/vrgda.cairo index 9c0bb08..7a15655 100644 --- a/src/vrgda.cairo +++ b/src/vrgda.cairo @@ -52,7 +52,7 @@ mod VRGDA { struct Buy { buyer: ContractAddress, paid: u128, - received: u128, + sold: u128, } #[derive(starknet::Event, Drop)] @@ -90,24 +90,24 @@ mod VRGDA { let amount_sold = self.amount_sold.read(); - // todo: compute the amount received based on current time - let received: u128 = 0; + // todo: compute the amount sold based on current time, the sales config, and the amount sold thus far + let sold: u128 = 0; - assert(received >= min_amount_out, 'PURCHASED'); + assert(sold >= min_amount_out, 'PURCHASED'); // Account for the newly sold amount before transferring anything - self.amount_sold.write(amount_sold + received); + self.amount_sold.write(amount_sold + sold); assert( - received.into() <= (amount_sold.into() + ot.balanceOf(get_contract_address())), + sold.into() <= (amount_sold.into() + ot.balanceOf(get_contract_address())), 'INSUFFICIENT_OPTIONS' ); - self.emit(Buy { buyer: get_caller_address(), paid, received }); + self.emit(Buy { buyer: get_caller_address(), paid, sold }); - st.transfer(get_caller_address(), received.into()); + st.transfer(get_caller_address(), sold.into()); - received + sold } fn clear_payment(ref self: ContractState) -> u256 { From a40a0f5de7ba6e5b384ba1db99899e8716e2332e Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 24 Oct 2023 23:05:23 -0400 Subject: [PATCH 3/4] improve style some more --- src/vrgda.cairo | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/vrgda.cairo b/src/vrgda.cairo index 7a15655..3661239 100644 --- a/src/vrgda.cairo +++ b/src/vrgda.cairo @@ -6,8 +6,8 @@ trait IVRGDA { // The result is transferred to the recipient fn buy(ref self: TContractState, min_amount_out: u128) -> u128; - // Refund any payment token that is still held by the contract - fn clear_payment(ref self: TContractState) -> u256; + // Withdraw the proceeds, must be called by the benefactor + fn withdraw_proceeds(ref self: TContractState); } #[starknet::interface] @@ -44,6 +44,9 @@ mod VRGDA { sale_config: SaleConfig, // The address that receives the payment token from the sale of the tokens benefactor: ContractAddress, + // The amount of tokens that have been used to purchase the sold token and have not been withdrawn + // Used to compute how much was paid + reserves: u128, // The number of sold tokens thus far amount_sold: u128, } @@ -80,14 +83,21 @@ mod VRGDA { #[external(v0)] impl VRGDAImpl of IVRGDA { fn buy(ref self: ContractState, min_amount_out: u128) -> u128 { - let pt = self.payment_token.read(); - let ot = self.option_token.read(); - let st = self.sold_token.read(); - let paid: u128 = pt + let payment_token = self.payment_token.read(); + let option_token = self.option_token.read(); + let sold_token = self.sold_token.read(); + + let reserves = self.reserves.read(); + + let payment_balance: u128 = payment_token .balanceOf(get_contract_address()) .try_into() .expect('PAID_OVERFLOW'); + let paid: u128 = payment_balance - reserves; + + self.reserves.write(payment_balance); + let amount_sold = self.amount_sold.read(); // todo: compute the amount sold based on current time, the sales config, and the amount sold thus far @@ -99,22 +109,25 @@ mod VRGDA { self.amount_sold.write(amount_sold + sold); assert( - sold.into() <= (amount_sold.into() + ot.balanceOf(get_contract_address())), - 'INSUFFICIENT_OPTIONS' + sold + .into() <= (amount_sold.into() + + option_token.balanceOf(get_contract_address())), + 'INSUFFICIENT_Opayment_tokenIONS' ); self.emit(Buy { buyer: get_caller_address(), paid, sold }); - st.transfer(get_caller_address(), sold.into()); + sold_token.transfer(get_caller_address(), sold.into()); sold } - fn clear_payment(ref self: ContractState) -> u256 { - let pt = self.payment_token.read(); - let refund = pt.balanceOf(get_contract_address()); - pt.transfer(get_caller_address(), refund); - refund + fn withdraw_proceeds(ref self: ContractState) { + let benefactor = self.benefactor.read(); + assert(get_caller_address() == benefactor, 'BENEFACTOR_ONLY'); + let reserves = self.reserves.read(); + self.reserves.write(0); + self.payment_token.read().transfer(benefactor, reserves.into()); } } } From 4e24d59a062f0b72c100a1fe3d2253517936be3a Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Fri, 27 Oct 2023 15:09:25 -0400 Subject: [PATCH 4/4] vrgda math --- Scarb.lock | 8 ++ Scarb.toml | 1 + src/lib.cairo | 1 + src/tests.cairo | 2 + src/tests/vrgda_test.cairo | 114 ++++++++++++++++++++++++++++ src/token_vrgda.cairo | 141 ++++++++++++++++++++++++++++++++++ src/vrgda.cairo | 151 +++++++------------------------------ 7 files changed, 296 insertions(+), 122 deletions(-) create mode 100644 src/tests/vrgda_test.cairo create mode 100644 src/token_vrgda.cairo diff --git a/Scarb.lock b/Scarb.lock index b048132..6ba0fec 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,6 +1,14 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "cubit" +version = "1.2.0" +source = "git+https://github.com/raphaeldkhn/cubit#e6331ebf98c5d5f442a0e5edefe0b367c8e270d9" + [[package]] name = "governance" version = "0.1.0" +dependencies = [ + "cubit", +] diff --git a/Scarb.toml b/Scarb.toml index 4399535..e99dbb2 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -6,6 +6,7 @@ homepage = "https://ekubo.org" cairo-version = "2.3.0" [dependencies] +cubit = { git = "https://github.com/raphaeldkhn/cubit" } starknet = "=2.3.0" [[target.starknet-contract]] diff --git a/src/lib.cairo b/src/lib.cairo index 5484297..14e299e 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -3,6 +3,7 @@ mod governor; mod governance_token; mod timelock; mod call_trait; +mod token_vrgda; mod vrgda; #[cfg(test)] diff --git a/src/tests.cairo b/src/tests.cairo index 91004bf..6a6f838 100644 --- a/src/tests.cairo +++ b/src/tests.cairo @@ -10,3 +10,5 @@ mod timelock_test; mod call_trait_test; #[cfg(test)] mod utils; +#[cfg(test)] +mod vrgda_test; diff --git a/src/tests/vrgda_test.cairo b/src/tests/vrgda_test.cairo new file mode 100644 index 0000000..f7eab0e --- /dev/null +++ b/src/tests/vrgda_test.cairo @@ -0,0 +1,114 @@ +use cubit::f128::{Fixed, FixedTrait}; +use governance::vrgda::{VRGDAParameters, VRGDAParametersTrait}; +use debug::{PrintTrait}; + + +#[test] +#[available_gas(30000000)] +fn test_decay_constant() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .decay_constant() == FixedTrait::new(12786309186476892720, true), + 'decay_constant' + ); +} + +#[test] +#[available_gas(30000000)] +fn test_p_at_time_zero() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .p( + time_units_since_start: FixedTrait::ZERO(), sold: 0 + ) == FixedTrait::new(0x17154754c6a1bf740, false), + 'p' + ); +} + +#[test] +#[available_gas(30000000)] +fn test_p_at_time_one() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .p( + time_units_since_start: FixedTrait::ONE(), sold: 0 + ) == FixedTrait::new(0xb8aa3aa6350dfba0, false), + 'p' + ); +} + +#[test] +#[available_gas(30000000)] +fn test_p_time_one_more_sold_per_time_unit() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1000, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .p( + time_units_since_start: FixedTrait::ONE(), sold: 0 + ) == FixedTrait::new(0x8009f8be9ba94b6f, false), + 'p' + ); +} + +#[test] +#[available_gas(30000000)] +fn test_p_time_zero_many_sold() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1000, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .p( + time_units_since_start: FixedTrait::ZERO(), sold: 1000 + ) == FixedTrait::new(0x20032fd0c57d40b42, false), + 'p' + ); +} + +#[test] +#[available_gas(30000000)] +fn test_p_sold_on_schedule() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1000, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .p( + time_units_since_start: FixedTrait::ZERO(), sold: 1000 + ) == FixedTrait::new(0x20032fd0c57d40b42, false), + 'p' + ); +} + +#[test] +#[available_gas(30000000)] +fn test_quote_batch_sold_example_schedule() { + assert( + VRGDAParameters { + target_price: FixedTrait::ONE(), + num_sold_per_time_unit: 1000, + price_decay_percent: FixedTrait::ONE() / FixedTrait::new_unscaled(2, false) + } + .quote_batch( + time_units_since_start: FixedTrait::ZERO(), sold: 0, amount: 10 + ) == FixedTrait::new(185108235227361442154, false), // ~= 10.0347 + 'p' + ); +} diff --git a/src/token_vrgda.cairo b/src/token_vrgda.cairo new file mode 100644 index 0000000..f76cd2c --- /dev/null +++ b/src/token_vrgda.cairo @@ -0,0 +1,141 @@ +use starknet::{ContractAddress}; +use governance::vrgda::{VRGDAParameters, VRGDAParametersTrait}; + +#[starknet::interface] +trait ITokenVRGDA { + // Buy the token with the ETH transferred to this contract, receiving at least min_amount_out + // The result is transferred to the recipient + fn buy(ref self: TContractState, buy_amount: u64, max_amount_in: u128) -> u128; + + // Withdraw the proceeds, must be called by the benefactor + fn withdraw_proceeds(ref self: TContractState); +} + +#[starknet::interface] +trait IERC20 { + fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} + +// The buyer must transfer this token to the VRGDA contract before calling buy +#[starknet::interface] +trait IOptionToken { + fn burn(ref self: TContractState, amount: u128); +} + +#[starknet::contract] +mod TokenVRGDA { + use super::{ + ContractAddress, ITokenVRGDA, IERC20Dispatcher, IERC20DispatcherTrait, + IOptionTokenDispatcher, IOptionTokenDispatcherTrait, VRGDAParameters, VRGDAParametersTrait + }; + use cubit::f128::{Fixed, FixedTrait}; + use starknet::{get_caller_address, get_contract_address, contract_address_const}; + + #[storage] + struct Storage { + // The token that is used to buy the token + payment_token: IERC20Dispatcher, + // The token that gives the user the right to purchase the sold token + option_token: IOptionTokenDispatcher, + // The token that is sold + sold_token: IERC20Dispatcher, + // the configuration of the vrgda + vrgda_parameters: VRGDAParameters, + // The address that receives the payment token from the sale of the tokens + benefactor: ContractAddress, + // The amount of tokens that have been used to purchase the sold token and have not been withdrawn + // Used to compute how much was paid + reserves: u128, + // The number of sold tokens thus far + amount_sold: u128, + } + + #[derive(starknet::Event, Drop)] + struct Buy { + buyer: ContractAddress, + paid: u128, + sold: u128, + } + + #[derive(starknet::Event, Drop)] + #[event] + enum Event { + Buy: Buy, + } + + #[constructor] + fn constructor( + ref self: ContractState, + payment_token: IERC20Dispatcher, + option_token: IOptionTokenDispatcher, + sold_token: IERC20Dispatcher, + vrgda_parameters: VRGDAParameters, + benefactor: ContractAddress + ) { + self.payment_token.write(payment_token); + self.option_token.write(option_token); + self.sold_token.write(sold_token); + self.vrgda_parameters.write(vrgda_parameters); + self.benefactor.write(benefactor); + } + + #[external(v0)] + impl TokenVRGDAImpl of ITokenVRGDA { + fn buy(ref self: ContractState, buy_amount: u64, max_amount_in: u128) -> u128 { + let payment_token = self.payment_token.read(); + let option_token = self.option_token.read(); + let sold_token = self.sold_token.read(); + + let reserves = self.reserves.read(); + + let payment_balance: u128 = payment_token + .balanceOf(get_contract_address()) + .try_into() + .expect('PAID_OVERFLOW'); + + let paid: u128 = payment_balance - reserves; + + self.reserves.write(payment_balance); + + let amount_sold = self.amount_sold.read(); + + let quote = self + .vrgda_parameters + .read() + .quote_batch( + time_units_since_start: FixedTrait::new(0, false), + sold: amount_sold.try_into().unwrap(), + amount: buy_amount + ); + let sold: u128 = 0; + + // assert(quote >= max_amount_in, 'PURCHASED'); + + // Account for the newly sold amount before transferring anything + self.amount_sold.write(amount_sold + sold); + + // assert( + // sold + // .into() <= (amount_sold.into() + // + option_token.balanceOf(get_contract_address())), + // 'INSUFFICIENT_OPTION_TOKENS' + // ); + + self.emit(Buy { buyer: get_caller_address(), paid, sold }); + + sold_token.transfer(get_caller_address(), sold.into()); + + sold + } + + fn withdraw_proceeds(ref self: ContractState) { + let benefactor = self.benefactor.read(); + assert(get_caller_address() == benefactor, 'BENEFACTOR_ONLY'); + let reserves = self.reserves.read(); + self.reserves.write(0); + self.payment_token.read().transfer(benefactor, reserves.into()); + } + } +} + diff --git a/src/vrgda.cairo b/src/vrgda.cairo index 3661239..e8879fa 100644 --- a/src/vrgda.cairo +++ b/src/vrgda.cairo @@ -1,134 +1,41 @@ -use starknet::{ContractAddress}; - -#[starknet::interface] -trait IVRGDA { - // Buy the token with the ETH transferred to this contract, receiving at least min_amount_out - // The result is transferred to the recipient - fn buy(ref self: TContractState, min_amount_out: u128) -> u128; - - // Withdraw the proceeds, must be called by the benefactor - fn withdraw_proceeds(ref self: TContractState); -} - -#[starknet::interface] -trait IERC20 { - fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; - fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; -} +use cubit::f128::{Fixed, FixedTrait}; #[derive(Drop, Copy, Serde, starknet::Store)] -struct SaleConfig { - // When the sales should begin, in epoch seconds - start_time: u64, - // How long the sale will last, in seconds - duration: u64, - // A 64 bit number indicating the number of tokens that should be sold per second - // The token is assumed to have 18 decimals, so this number is not a fractional quantity, i.e. 1 represents 1e-18 tokens per second - sell_rate: u64, +struct VRGDAParameters { + // The price at which the VRGDA should aim to sell the tokens + target_price: Fixed, + // How many tokens should be sold per time unit, in combination with the target price determines the rate + num_sold_per_time_unit: u64, + // How the price decays per time unit, if none are sold + price_decay_percent: Fixed, } -#[starknet::contract] -mod VRGDA { - use super::{SaleConfig, ContractAddress, IVRGDA, IERC20Dispatcher, IERC20DispatcherTrait}; - use starknet::{get_caller_address, get_contract_address, contract_address_const}; - - #[storage] - struct Storage { - // The token that is used to buy the token - payment_token: IERC20Dispatcher, - // The token that gives the user the right to purchase the sold token - option_token: IERC20Dispatcher, - // The token that is sold - sold_token: IERC20Dispatcher, - // the configuration of the sale - sale_config: SaleConfig, - // The address that receives the payment token from the sale of the tokens - benefactor: ContractAddress, - // The amount of tokens that have been used to purchase the sold token and have not been withdrawn - // Used to compute how much was paid - reserves: u128, - // The number of sold tokens thus far - amount_sold: u128, +#[generate_trait] +impl VRGDAParametersTraitImpl of VRGDAParametersTrait { + fn decay_constant(self: VRGDAParameters) -> Fixed { + (FixedTrait::ONE() - self.price_decay_percent).ln() } - #[derive(starknet::Event, Drop)] - struct Buy { - buyer: ContractAddress, - paid: u128, - sold: u128, + fn p_integral(self: VRGDAParameters, time_units_since_start: Fixed, sold: u64) -> Fixed { + -(self.target_price + * FixedTrait::new_unscaled(self.num_sold_per_time_unit.into(), false) + * (FixedTrait::ONE() - self.price_decay_percent) + .pow( + time_units_since_start + - (FixedTrait::new_unscaled(sold.into(), false) + / FixedTrait::new_unscaled(self.num_sold_per_time_unit.into(), false)) + ) + / self.decay_constant()) } - #[derive(starknet::Event, Drop)] - #[event] - enum Event { - Buy: Buy, + fn quote_batch( + self: VRGDAParameters, time_units_since_start: Fixed, sold: u64, amount: u64 + ) -> Fixed { + self.p_integral(time_units_since_start, sold + amount) + - self.p_integral(time_units_since_start, sold) } - #[constructor] - fn constructor( - ref self: ContractState, - payment_token: IERC20Dispatcher, - option_token: IERC20Dispatcher, - sold_token: IERC20Dispatcher, - sale_config: SaleConfig, - benefactor: ContractAddress - ) { - self.payment_token.write(payment_token); - self.option_token.write(option_token); - self.sold_token.write(sold_token); - self.sale_config.write(sale_config); - self.benefactor.write(benefactor); - } - - #[external(v0)] - impl VRGDAImpl of IVRGDA { - fn buy(ref self: ContractState, min_amount_out: u128) -> u128 { - let payment_token = self.payment_token.read(); - let option_token = self.option_token.read(); - let sold_token = self.sold_token.read(); - - let reserves = self.reserves.read(); - - let payment_balance: u128 = payment_token - .balanceOf(get_contract_address()) - .try_into() - .expect('PAID_OVERFLOW'); - - let paid: u128 = payment_balance - reserves; - - self.reserves.write(payment_balance); - - let amount_sold = self.amount_sold.read(); - - // todo: compute the amount sold based on current time, the sales config, and the amount sold thus far - let sold: u128 = 0; - - assert(sold >= min_amount_out, 'PURCHASED'); - - // Account for the newly sold amount before transferring anything - self.amount_sold.write(amount_sold + sold); - - assert( - sold - .into() <= (amount_sold.into() - + option_token.balanceOf(get_contract_address())), - 'INSUFFICIENT_Opayment_tokenIONS' - ); - - self.emit(Buy { buyer: get_caller_address(), paid, sold }); - - sold_token.transfer(get_caller_address(), sold.into()); - - sold - } - - fn withdraw_proceeds(ref self: ContractState) { - let benefactor = self.benefactor.read(); - assert(get_caller_address() == benefactor, 'BENEFACTOR_ONLY'); - let reserves = self.reserves.read(); - self.reserves.write(0); - self.payment_token.read().transfer(benefactor, reserves.into()); - } + fn p(self: VRGDAParameters, time_units_since_start: Fixed, sold: u64) -> Fixed { + self.quote_batch(time_units_since_start, sold, amount: 1) } } -