Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto return stake in tg4 contract #124

Merged
merged 14 commits into from
Sep 16, 2021
Merged

Auto return stake in tg4 contract #124

merged 14 commits into from
Sep 16, 2021

Conversation

hashedone
Copy link
Contributor

@hashedone hashedone commented Sep 9, 2021

Closes #113
Closes #142 (via #146 merged in)

@hashedone hashedone marked this pull request as draft September 9, 2021 09:14

use crate::error::ContractError;
use crate::msg::{
ExecuteMsg, InstantiateMsg, PreauthResponse, QueryMsg, StakedResponse, UnbondingPeriodResponse,
};
use crate::state::{members, Config, ADMIN, CLAIMS, CONFIG, HOOKS, PREAUTH, STAKE, TOTAL};

pub type Response = cosmwasm_std::Response<TgradeMsg>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Contributor

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor hints (that you probably know, but just writing it down)

let config = CONFIG.load(deps.storage)?;

if config.auto_return_limit > 0 {
let msgs = request_privileges(&[Privilege::EndBlocker]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to request this - and only if there is a reason to do so.

}

fn end_block(deps: DepsMut) -> Result<Response, ContractError> {
// TODO: Auto return claims
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yes, this requires copying over Claims from cw-plus and modifying it with another index... tricky one to be sure.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I'm doing copy of Claims in #139

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to be merged first, so basically I would align to your changes with this.

}

impl<'a> Claims<'a> {
pub fn new(storage_key: &'a str) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

@hashedone hashedone force-pushed the 113-auto-return-stake branch from 8b4ce43 to 74e9373 Compare September 14, 2021 10:57
Copy link
Contributor

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. This looks mostly there.

The big issues I would raise are:

  1. Using ADMIN directly from cw-plus v0.9.0 (which can be left as a TODO for another PR... your copy works fine for now, but let's remove code we don't need)
  2. Better use of primary key. Indexes are awesome, you can make it even better 😄 ping me on discord if what I wrote didn't make sense
  3. Merging in the work from Jakub from Store creation height for undelegate #139, so it doesn't get lost/overwritten

Happy to review tomorrow morning. This looks quite close to done

@@ -0,0 +1,162 @@
//! This utility is based on `cw_controllers::Claim`, but tweaked to be more compliant to what is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already copied over (and modified) in #139 (which was now merged).

I would remove this commit (stash it?), rebase on top of origin/main, and then modify the version Jakub worked on. Otherwise, this is just asking for a merge conflict. (and loss of the functionality from #139)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... this is quite some diff... maybe you cannot rebase, but need to merge it in and resolve conflicts manually

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At morning I succesfully rebased it, but if there would be a problem I would just rebase manually, it should not be this much a pain (at least comments, other stuff may be).

}

struct ClaimIndexes<'a> {
pub addr: MultiIndex<'a, (Addr, Vec<u8>), Claim>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the Primary Key, we do not need a secondary index for this.
Just release_at as a secondary index

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was old code, not including my todays changes.


pub struct Claims<'a> {
/// Claims are indexed by arbitrary numeric index, so every claim has its own entry
claims: IndexedMap<'a, U64Key, Claim, ClaimIndexes<'a>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than an arbitrary U64Key, please use (&Addr, Timestamp) or such here.

Each secondary index has a rather non-trivial cost to read and write, let's make use of the primary key for an index when we can, rather than a meaningless counter here.

storage_key,
&format!("{}__addr", storage_key),
),
release_at: MultiIndex::new(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job constructing this.

};

let claims = IndexedMap::new(storage_key, indexes);
let next_key = Item::new(&format!("{}__key", storage_key));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I would remove the next_key and just use the indexed map with a meaningful primary key

(Some(cap), Ok((_, sum))) => cap <= (*sum).into(),
_ => true,
})
// need to collet intermediately, cannot remove from map while iterating as it borrows
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice chain here.

// take all claims for the addr
.range(storage, None, None, Order::Ascending)
// filter out non-expired claims (leaving errors to stop on first
.filter(|claim| match claim {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With compound index (Addr, U64Key), you can use the range query to only get expired Claims, and not every iterate over the others. more efficient

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code sample above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is still inefficient. Using a .range(storage, None, end, Order::Ascending) where end is the current block time would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I missed it here.

// removes item from storage and returns accumulated sum
.try_fold(0, |_, (idx, sum)| -> StdResult<_> {
self.claims.remove(storage, idx.into())?;
Ok(sum)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh, don't you need to use sum + _ here?
actually, that scan confused me a bit above, maybe you don't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, scan already accumulated things, I would definetely simplify it.

@@ -73,7 +77,21 @@ pub fn execute(
let api = deps.api;
match msg {
ExecuteMsg::UpdateAdmin { admin } => {
Ok(ADMIN.execute_update_admin(deps, info, maybe_addr(api, admin)?)?)
let resp = ADMIN.execute_update_admin(deps, info, maybe_addr(api, admin)?)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you upgrade deps to cw-plus v0.9.0, you can use the ADMIN from cw-plus unmodified, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this comment before, would apply it at evening/tomorrow, but it is not critical for logic

@hashedone hashedone force-pushed the 113-auto-return-stake branch from ffd1ea2 to d4fabbe Compare September 15, 2021 11:20
@hashedone hashedone force-pushed the 113-auto-return-stake branch from d4fabbe to 2d0ac50 Compare September 15, 2021 12:40
Comment on lines 118 to 117
Ok((_, claim)) => claim.release_at <= block.time,
Ok((_, claim)) => claim.release_at.is_expired(&block),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines 4 to 5
use tg_controllers::Admin;
use tg_controllers::{Hooks, Preauth};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Join those.

Comment on lines 330 to 331
use cosmwasm_std::testing::{mock_env, MockApi, MockStorage};
use cosmwasm_std::{coins, Addr, Empty, Uint128};
use cosmwasm_std::{coins, Addr, Uint128};
Copy link

@ueco-jb ueco-jb Sep 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Join those as well? I actually don't know what format are we using here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cosmwasm_std and cosmwasm_std::testing are usually kept separately (test and non-test).

The other comment above I agree with

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, I suspected such.

Comment on lines 48 to 76
creation_height: u64,
creation_heigh: u64,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was correct (also in couple more places).

Comment on lines +10 to +12
const fn default_auto_return_limit() -> u64 {
20
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why function instead of just variable? Won't compiler just inline it in the end anyway?

Comment on lines 103 to 109
let control = Admin::new("foo");

// initialize and check
let admin = Some(Addr::unchecked("admin"));
control.set(deps.as_mut(), admin.clone()).unwrap();
let got = control.get(deps.as_ref()).unwrap();
assert_eq!(admin, got);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking: I'd rename control to just admin and admin to hungarian (wink) admin_addr for better clarity
Then you could have
assert_eq!(admin_addr, admin.get(deps.as_ref()).unwrap());
(this got doesn't "speak" to me)

Also I have some issue here, without reading implementation it's not clear that those get/set methods are for reading/assigning addresses - maybe there's room for improvement?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is just admin copied from cw-plus, then let's make an issue there and let this continue as is.

Most likely, this file will be removed and we can just import the cw-controllers::Admin from cw-plus v0.9.0, which has support for generic Response

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hashedone hashedone force-pushed the 113-auto-return-stake branch from 8e9bec0 to 743302e Compare September 15, 2021 14:40
@hashedone hashedone marked this pull request as ready for review September 15, 2021 14:41
Copy link
Contributor

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks quite good.

There are a number of comments for a follow up PR to improve the efficiency or code style.

I would just ask for 2 now, so we can merge it (and merge others on top).

  1. heigh -> height properly named.
  2. undo changes to tg4-group and tg4-mixer non-test code (keep them with Response)

when you finish those, I am happy to merge. And then you can start a new PR for the other issues.

@@ -21,10 +21,11 @@ library = []
[dependencies]
cw0 = { version = "0.8.0" }
cw2 = { version = "0.8.0" }
cw-controllers = { version = "0.8.0" }
cw-controllers = { version = "0.9.0" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... this seems like a partial implementation of #142
I guess we merge main into #146 after this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I merged this in, worked fine. Now everything is up to v0.9.0

@@ -14,6 +12,10 @@ use tg4::{
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, PreauthResponse, QueryMsg, SudoMsg};
use crate::state::{members, ADMIN, HOOKS, PREAUTH, TOTAL};
use tg_bindings::TgradeMsg;

pub type Response = cosmwasm_std::Response<TgradeMsg>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use these types here.
I would use Response unless we make use of Tgrade features.
I think you only add them to tg4-stake.

@@ -346,7 +349,7 @@ mod tests {
}
}

pub fn contract_mixer() -> Box<dyn Contract<Empty>> {
pub fn contract_mixer() -> Box<dyn Contract<TgradeMsg>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to change the contracts to make the types match.

There is ContractWrapper::new_with_empty, which takes a contract that returns Response and converts it to Contract for any T.

I'd prefer this over artificially adding the Tgrade Response types to tg4-group and tg4-mixer.

But yeah, in the multitest that uses them all (this file), we need to use adaptors so the App supports TgradeMsg (as I describe on the first line).

@@ -382,7 +385,7 @@ mod tests {
}

// uploads code and returns address of group contract
fn instantiate_group(app: &mut App, members: Vec<Member>) -> Addr {
fn instantiate_group(app: &mut App<TgradeMsg>, members: Vec<Member>) -> Addr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is needed. (and all App changes)

pub release_at: Expiration,
pub creation_height: u64,
/// Height of a blockchain in a moment of creation of this claim
pub creation_heigh: u64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change. We want the ending t

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update: I fixed the spelling

QueryMsg::Claims { address } => {
to_binary(&CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?)?)
}
QueryMsg::Claims { address } => to_binary(&ClaimsResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to become a proper paginated list query... in a follow-up PR

@@ -454,7 +454,7 @@ mod test {
}

fn contract_group() -> Box<dyn Contract<TgradeMsg>> {
let contract = ContractWrapper::new_with_empty(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a nice way to do it without forcing the upgrade to tg4-group.
I would undo any changes to that contract and just use new_with_empty here and in the mixer test.

.range(
storage,
None,
Some(Bound::inclusive(self.claims.idx.release_at.index_key((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty sure ExpirationKey::new(Expiration::now(block)).0 will work (or U64Key::from(block.time))

Can you make the change and see if tests pass.

Order::Ascending,
);

let mut claims = self.filter_claims(claims, None, limit.into())?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove this filter_claims, in this case, we always have the cash to cover them.

let mut claims = self.filter_claims(claims, None, limit.into())?;
claims.sort_by_key(|claim| claim.addr.clone());

let releases = claims
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice grouping 👍

@ethanfrey ethanfrey mentioned this pull request Sep 15, 2021
7 tasks
Copy link
Contributor

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I made #148 for the type cleanup I want.

If you import TgradeMsg, you trigger another export that limits where the contract can deploy. I try to do that sparingly. But maybe you think it is better to make everything tied to Tgrade, and maybe you are right.

Take a look at it. You can merge #148 into this, and then this to main. Or just this into main as is.

I would like to get this in main. Follow up parts to finalise it are tracked in #147 and you can add to that.

@ethanfrey
Copy link
Contributor

Reflecting some more... you are right using the TgradeMsg in all these contracts. This repo is designed to run on Tgrade and nowhere else. I am still in the generic cw-plus mindset.

I will close #148 and merge this.

@ethanfrey ethanfrey merged commit 60d467e into main Sep 16, 2021
@ethanfrey ethanfrey deleted the 113-auto-return-stake branch September 16, 2021 05:30
// take all claims for the addr
.range(storage, None, None, Order::Ascending)
// filter out non-expired claims (leaving errors to stop on first
.filter(|claim| match claim {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is still inefficient. Using a .range(storage, None, end, Order::Ascending) where end is the current block time would be better.

Err(_) => true,
});

let claims = self.filter_claims(claims, cap.map(u128::from), None)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a cap at all? A cap and a limit sound like too much / unnecessary to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cap came from my old code. I commented to remove this.

}

/// Releases given claims by removing them from storage
fn release_claims(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would call this remove_claims.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea. or maybe delete_claims

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Upgrade to cw-plus v0.9.0 Use Begin/End Block handler to auto-return stake after unbonding period
4 participants