@@ -29,6 +29,10 @@ const EPendingWithdrawal: u64 = 5;
2929const ERemovingDefaultTerm : u64 = 6 ;
3030/// Error when user choose a term with apy less than expectation;
3131const EApyMismatched : u64 = 7 ;
32+ /// Error when user tries to extend term when the pool not support it;
33+ const ENotSupportExtendTerm : u64 = 8 ;
34+ /// Error when user tries to extend term with wrong parameters;
35+ const EInvalidExtendTerm : u64 = 9 ;
3236
3337// ====================== Const =================
3438/// Current version of the contract
@@ -43,6 +47,9 @@ const KEY_SUPPORT_EARLY_WITHDRAWAL: u8 = 1;
4347/// Option key for withdrawal pending window (days: u32), optional.
4448const KEY_WITHDRAWAL_PENDING : u8 = 2 ;
4549
50+ /// Option key for enable upgrade the term for higher apy (bool), optional.
51+ const KEY_SUPPORT_TERM_EXTENSION : u8 = 3 ;
52+
4653public struct AdminCap has key , store {
4754 id: UID ,
4855}
@@ -131,9 +138,9 @@ public fun deposit<Base, Loyalty>(
131138 assert ! (pool.version == VERSION , EWrongVersion );
132139
133140 let (lock_term, apy) = if (pool.return_rates.contains (term)) {
134- (term as u64 , * pool.return_rates. borrow ( term) )
141+ (term as u64 , pool.return_rates[ term] )
135142 } else {
136- (0 , * pool.return_rates. borrow ( 0 ) )
143+ (0 , pool.return_rates[ 0 ] )
137144 };
138145
139146 // if expected_apy has some value, ensure fetched apy is higer.
@@ -166,7 +173,7 @@ entry fun withdraw<Base, Loyalty>(
166173
167174 // Check eligible for execute withdrawal. Option always exists as this immutable option
168175 // was added during pool creation
169- if (pool.options. borrow ( KEY_SUPPORT_EARLY_WITHDRAWAL )!= true ) {
176+ if (! pool.options[ KEY_SUPPORT_EARLY_WITHDRAWAL ] ) {
170177 assert ! (clock.timestamp_ms () >= receipt.mature_at_ms, ENotSupportEarlyWithdrawal );
171178 };
172179
@@ -181,7 +188,7 @@ entry fun withdraw<Base, Loyalty>(
181188 df::add (
182189 &mut pool.id,
183190 id (&receipt),
184- clock.timestamp_ms () + * pool.options. borrow ( KEY_WITHDRAWAL_PENDING ) ,
191+ clock.timestamp_ms () + pool.options[ KEY_WITHDRAWAL_PENDING ] ,
185192 );
186193 transfer::transfer (receipt, ctx.sender ());
187194 return
@@ -215,6 +222,41 @@ entry fun withdraw<Base, Loyalty>(
215222 transfer::public_transfer (pool.balance.split (amount).into_coin (ctx), ctx.sender ());
216223}
217224
225+ /// Upgrade the term of premature deposit receipt if support
226+ public fun upgrade_term <Base , Loyalty >(
227+ pool: &mut DepositPool <Base , Loyalty >,
228+ receipt: &mut Receipt ,
229+ target_term: u32 ,
230+ target_apy: Option <u16 >,
231+ clock: &Clock ,
232+ ) {
233+ assert ! (pool.version == VERSION , EWrongVersion );
234+ assert ! (receipt.pool_id == object::id (pool), EWrongPool );
235+
236+ // if option does not exists, directly abort
237+ assert ! (pool.options.contains (KEY_SUPPORT_TERM_EXTENSION ), ENotSupportExtendTerm );
238+
239+ // mature receipt cannot extend, thus if the receipt within withdrwal pending will
240+ // not get additional benefits. An early withdrawal pending will not get any token
241+ // so we can safely ignore the withdrawal pending situation.
242+ assert ! (receipt.mature_at_ms > clock.timestamp_ms (), EInvalidExtendTerm );
243+
244+ // new apy must exists
245+ let new_apy = pool.return_rates[target_term];
246+
247+ // To prevent misoperation, if user did not specify the apy, then new apy should be no less than the old one
248+ // However, user may explicilty choose a lower apy.
249+ assert ! (new_apy>=target_apy.destroy_or ! (receipt.apy), EApyMismatched );
250+
251+ let new_mature_at_ms = receipt.issue_at_ms + (target_term as u64 )*MS_PER_DAY ;
252+
253+ // mature term should be increase only.
254+ assert ! (new_mature_at_ms>receipt.mature_at_ms, EInvalidExtendTerm );
255+
256+ receipt.mature_at_ms = new_mature_at_ms;
257+ receipt.apy = new_apy;
258+ }
259+
218260/// Updates or adds a new lock term period with corresponding APY
219261public fun upsert_lock_term <Base , Loyalty >(
220262 pool: &mut DepositPool <Base , Loyalty >,
@@ -257,6 +299,27 @@ public fun delete_lock_term<Base, Loyalty>(
257299 pool.return_rates.remove (days);
258300}
259301
302+ public fun enable_extending_terms <Base , Loyalty >(
303+ pool: &mut DepositPool <Base , Loyalty >,
304+ admin: &mut AdminCap ,
305+ ) {
306+ assert ! (pool.admin_cap_id == object::id (admin), ENotAdmin );
307+ assert ! (pool.version == VERSION , EWrongVersion );
308+
309+ pool.options.add (KEY_SUPPORT_TERM_EXTENSION , true );
310+ }
311+
312+ public fun disable_extending_terms <Base , Loyalty >(
313+ pool: &mut DepositPool <Base , Loyalty >,
314+ admin: &mut AdminCap ,
315+ ) {
316+ assert ! (pool.admin_cap_id == object::id (admin), ENotAdmin );
317+ assert ! (pool.version == VERSION , EWrongVersion );
318+
319+ // abort if not exists, then no meaning to disable it.
320+ pool.options.remove <u8 , bool >(KEY_SUPPORT_TERM_EXTENSION );
321+ }
322+
260323/// Adds a new reward pool policy for loyalty tokens
261324#[allow(lint(self_transfer))]
262325public fun add_reward_program <Policy : drop , Base , Loyalty >(
@@ -298,7 +361,7 @@ fun calculate_token_amount<Base, Loyalty>(
298361 apy: u16 ,
299362): u64 {
300363 let withdraw_at_ms = if (pool.options.contains (KEY_WITHDRAWAL_PENDING )) {
301- df::remove (&mut pool.id, receipt_id) - * pool.options. borrow ( KEY_WITHDRAWAL_PENDING )
364+ df::remove (&mut pool.id, receipt_id) - pool.options[ KEY_WITHDRAWAL_PENDING ]
302365 } else {
303366 clock.timestamp_ms ()
304367 };
0 commit comments