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

feat: updated withdraw fund logic to withdraw the whole balance #509

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,778 changes: 3,302 additions & 476 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ members = [
"contracts/os/*",

#Internal
"tests-integration",
"tests-integration", "ibc-tests",
]
resolver = "2"

Expand All @@ -38,6 +38,7 @@ andromeda-modules = { path = "./packages/andromeda-modules", version = "2.0.0" }
andromeda-app = { path = "./packages/andromeda-app", version = "1.0.0" }
andromeda-ecosystem = { path = "./packages/andromeda-ecosystem", version = "1.0.0" }
andromeda-testing = { path = "./packages/andromeda-testing", version = "1.0.0" }
andromeda-testing-e2e = { path = "./packages/andromeda-testing-e2e", version = "1.0.0" }


strum_macros = "0.24.3"
Expand Down
59 changes: 59 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
clean:
-starship stop --config ibc-tests/configs/starship.yaml
-pkill -f "port-forward"

restart-cluster:
-kind delete cluster --name starship
-kind create cluster --name starship
-kubectl cluster-info --context kind-starship
start:
-starship start --config ibc-tests/configs/starship.yaml



# loom
# HERMES_ENABLED=false

# .PHONY: build
# build:
# @DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose.yml build

# .PHONY: setup-chains
# setup-chains:
# ./setup_docker.sh setup_chains
# $(MAKE) build

# .PHONY: setup-chains-with-hermes
# setup-chains-with-hermes:
# ./setup_docker.sh setup_chains_with_hermes
# $(MAKE) build

# start-chains:
# $(MAKE) setup-chains
# @docker-compose -f docker-compose.yml up

# start-chainsd:
# $(MAKE) setup-chains
# @docker-compose -f docker-compose.yml up -d

# start-chains-with-hermes:
# setup-chains-with-hermes
# @docker-compose -f docker-compose.yml up

# start-chains-with-hermesd:
# setup-chains-with-hermes
# @docker-compose -f docker-compose.yml up -d

# .PHONY: stop
# stop:
# @docker-compose -f docker-compose.yml down -t 3


# restart: stop
# @docker-compose -f docker-compose.yml up --force-recreate

# restartd: stop
# @docker-compose -f docker-compose.yml up --force-recreate -d

# clean:
# @rm -rfI ./.config
116 changes: 71 additions & 45 deletions contracts/finance/andromeda-validator-staking/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::str::FromStr;

use crate::state::{DEFAULT_VALIDATOR, UNSTAKING_QUEUE};
use crate::{
state::{DEFAULT_VALIDATOR, UNSTAKING_QUEUE},
util::decode_unstaking_response_data,
};
use cosmwasm_std::{
coin, ensure, entry_point, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut,
DistributionMsg, Env, FullDelegation, MessageInfo, Reply, Response, StakingMsg, StdError,
SubMsg, Timestamp, Uint128,
coin, ensure, entry_point, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, DistributionMsg,
Env, FullDelegation, MessageInfo, Reply, Response, StakingMsg, StdError, SubMsg, Timestamp,
Uint128,
};
use cw2::set_contract_version;

Expand Down Expand Up @@ -84,7 +85,9 @@ pub fn handle_execute(ctx: ExecuteContext, msg: ExecuteMsg) -> Result<Response,
validator,
recipient,
} => execute_claim(ctx, validator, recipient),
ExecuteMsg::WithdrawFunds {} => execute_withdraw_fund(ctx),
ExecuteMsg::WithdrawFunds { denom, recipient } => {
execute_withdraw_fund(ctx, denom, recipient)
}

_ => ADOContract::default().execute(ctx, msg),
}
Expand Down Expand Up @@ -183,10 +186,20 @@ fn execute_unstake(
}
);

let fund = coin(unstake_amount.u128(), res.amount.denom);
let undelegate_msg = CosmosMsg::Staking(StakingMsg::Undelegate {
validator: validator.to_string(),
amount: coin(unstake_amount.u128(), res.amount.denom),
amount: fund.clone(),
});

let mut unstaking_queue = UNSTAKING_QUEUE.load(deps.storage).unwrap_or_default();
unstaking_queue.push(UnstakingTokens {
fund,
payout_at: Timestamp::default(),
});

UNSTAKING_QUEUE.save(deps.storage, &unstaking_queue)?;

let undelegate_msg = SubMsg::reply_on_success(undelegate_msg, ReplyId::ValidatorUnstake.repr());

let res = Response::new()
Expand Down Expand Up @@ -257,7 +270,11 @@ fn execute_claim(
Ok(res)
}

fn execute_withdraw_fund(ctx: ExecuteContext) -> Result<Response, ContractError> {
fn execute_withdraw_fund(
ctx: ExecuteContext,
denom: Option<String>,
recipient: Option<AndrAddr>,
) -> Result<Response, ContractError> {
let ExecuteContext {
deps, info, env, ..
} = ctx;
Expand All @@ -268,35 +285,38 @@ fn execute_withdraw_fund(ctx: ExecuteContext) -> Result<Response, ContractError>
ContractError::Unauthorized {}
);

let mut funds = Vec::<Coin>::new();
loop {
match UNSTAKING_QUEUE.front(deps.storage).unwrap() {
Some(UnstakingTokens { payout_at, .. }) if payout_at <= env.block.time => {
if let Some(UnstakingTokens { fund, .. }) =
UNSTAKING_QUEUE.pop_front(deps.storage)?
{
funds.push(fund)
}
}
_ => break,
}
}
let recipient = recipient.map_or(Ok(info.sender), |r| r.get_raw_address(&deps.as_ref()))?;
let funds = denom.map_or(
deps.querier
.query_all_balances(env.contract.address.clone())?,
|d| {
deps.querier
.query_balance(env.contract.address.clone(), d)
.map(|fund| vec![fund])
.expect("Invalid denom")
},
);

// Remove expired unstaking requests
let mut unstaking_queue = UNSTAKING_QUEUE.load(deps.storage)?;
unstaking_queue.retain(|token| token.payout_at >= env.block.time);
UNSTAKING_QUEUE.save(deps.storage, &unstaking_queue)?;

Comment on lines +300 to +303
Copy link
Contributor

Choose a reason for hiding this comment

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

Optimize the unstaking queue cleanup.

The current logic for removing expired unstaking requests is functional but could be optimized by using drain_filter if available in the standard library or a similar method to avoid loading and saving the entire queue.

unstaking_queue.retain(|token| token.payout_at >= env.block.time);

ensure!(
!funds.is_empty(),
ContractError::InvalidWithdrawal {
msg: Some("No unstaked funds to withdraw".to_string())
msg: Some("No funds to withdraw".to_string())
}
);

let res = Response::new()
.add_message(BankMsg::Send {
to_address: info.sender.to_string(),
to_address: recipient.to_string(),
amount: funds,
})
.add_attribute("action", "withdraw-funds")
.add_attribute("from", env.contract.address)
.add_attribute("to", info.sender.into_string());
.add_attribute("to", recipient.into_string());

Ok(res)
}
Expand All @@ -321,12 +341,7 @@ fn query_staked_tokens(
}

fn query_unstaked_tokens(deps: Deps) -> Result<Vec<UnstakingTokens>, ContractError> {
let iter = UNSTAKING_QUEUE.iter(deps.storage).unwrap();
let mut res = Vec::<UnstakingTokens>::new();

for data in iter {
res.push(data.unwrap());
}
let res = UNSTAKING_QUEUE.load(deps.storage)?;
Ok(res)
}

Expand All @@ -344,21 +359,32 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractE
}

pub fn on_validator_unstake(deps: DepsMut, msg: Reply) -> Result<Response, ContractError> {
let attributes = &msg.result.unwrap().events[0].attributes;
let mut fund = Coin::default();
let mut payout_at = Timestamp::default();
for attr in attributes {
if attr.key == "amount" {
fund = Coin::from_str(&attr.value).unwrap();
} else if attr.key == "completion_time" {
let completion_time = DateTime::parse_from_rfc3339(&attr.value).unwrap();
let seconds = completion_time.timestamp() as u64;
let nanos = completion_time.timestamp_subsec_nanos() as u64;
payout_at = Timestamp::from_seconds(seconds);
payout_at = payout_at.plus_nanos(nanos);
let res = msg.result.unwrap();
let mut unstaking_queue = UNSTAKING_QUEUE.load(deps.storage).unwrap_or_default();
let payout_at = if res.data.is_some() {
let data = res.data;
let (seconds, nanos) = decode_unstaking_response_data(data.unwrap());
let payout_at = Timestamp::from_seconds(seconds);
payout_at.plus_nanos(nanos)
} else {
let attributes = &res.events[0].attributes;
let mut payout_at = Timestamp::default();
for attr in attributes {
if attr.key == "completion_time" {
let completion_time = DateTime::parse_from_rfc3339(&attr.value).unwrap();
let seconds = completion_time.timestamp() as u64;
let nanos = completion_time.timestamp_subsec_nanos() as u64;
payout_at = Timestamp::from_seconds(seconds);
payout_at = payout_at.plus_nanos(nanos);
}
}
}
UNSTAKING_QUEUE.push_back(deps.storage, &UnstakingTokens { fund, payout_at })?;
payout_at
};
let mut unstake_req = unstaking_queue.pop().unwrap();
unstake_req.payout_at = payout_at;

unstaking_queue.push(unstake_req);
UNSTAKING_QUEUE.save(deps.storage, &unstaking_queue)?;

Ok(Response::default())
}
2 changes: 1 addition & 1 deletion contracts/finance/andromeda-validator-staking/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pub mod contract;
pub mod state;

#[cfg(test)]
mod testing;
pub mod util;

#[cfg(all(not(target_arch = "wasm32"), feature = "testing"))]
pub mod mock;
9 changes: 6 additions & 3 deletions contracts/finance/andromeda-validator-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl MockValidatorStaking {
}

pub fn execute_withdraw_fund(&self, app: &mut MockApp, sender: Addr) -> ExecuteResult {
let msg = mock_execute_withdraw_fund();
let msg = mock_execute_withdraw_fund(None, None);
self.execute(app, &msg, sender, &[])
}

Expand Down Expand Up @@ -109,8 +109,11 @@ pub fn mock_execute_claim_reward(
}
}

pub fn mock_execute_withdraw_fund() -> ExecuteMsg {
ExecuteMsg::WithdrawFunds {}
pub fn mock_execute_withdraw_fund(
denom: Option<String>,
recipient: Option<AndrAddr>,
) -> ExecuteMsg {
ExecuteMsg::WithdrawFunds { denom, recipient }
}

pub fn mock_get_staked_tokens(validator: Option<Addr>) -> QueryMsg {
Expand Down
4 changes: 2 additions & 2 deletions contracts/finance/andromeda-validator-staking/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use andromeda_finance::validator_staking::UnstakingTokens;
use cw_storage_plus::{Deque, Item};
use cw_storage_plus::Item;

use cosmwasm_std::Addr;

pub const DEFAULT_VALIDATOR: Item<Addr> = Item::new("default_validator");

pub const UNSTAKING_QUEUE: Deque<UnstakingTokens> = Deque::new("unstaking_queue");
pub const UNSTAKING_QUEUE: Item<Vec<UnstakingTokens>> = Item::new("unstaking_queue");
82 changes: 82 additions & 0 deletions contracts/finance/andromeda-validator-staking/src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use cosmwasm_std::Binary;

pub fn decode_leb128(buf: &[u8]) -> u64 {
let mut ret: u64 = 0;
let mut offset = 0;

while offset < buf.len() {
let item: u8 = *buf.get(offset).unwrap_or(&0);
ret += u64::from(item) << (7 * offset);

if item < 0x80 {
return ret;
} else {
ret -= 0x80 << (7 * offset);
}
offset += 1;
}
ret
}

pub fn decode_unstaking_response_data(data: Binary) -> (u64, u64) {
/* extract seconds and nanoseconds from unstaking submessage reply data
the unstaking reply data structure is as follow


|--0 ~ 2--|-----------3 ~ 7----------|------|------------ 9~ --------------|
| headers | seconds in leb128 format | 0x10 | nano second in leb128 format |
Bytes 0 - 2 and 8 is used to identify proto tag and length, etc.
Bytes 3 - 7 represent seconds in LEB128 format.
Bytes 9 - represent nano seconds in LEB128 format.

Additional data can come after the nano second data depending on the cosmos sdk version used by the network. The decode algorithm will ignore additional data.

Check unstaking response proto here for additional information(https://docs.cosmos.network/v0.46/modules/staking/03_messages.html)
*/
let data = data.to_vec();
let seconds = decode_leb128(&data[3..8]);

let nano_seconds = decode_leb128(&data[9..]);
(seconds, nano_seconds)
}

#[test]
fn test_decode_leb128() {
let input = vec![0xd1, 0xfb, 0xc2, 0xb6, 0x06];
let output = decode_leb128(&input);
let expected_output = 1724956113;
assert_eq!(output, expected_output);

let input = vec![0xb8, 0xe3, 0xdd, 0xed, 0x02];
let output = decode_leb128(&input);
let expected_output = 766996920;
assert_eq!(output, expected_output);

let input = vec![0x9b, 0x96, 0xbd, 0xb6, 0x06];
let output = decode_leb128(&input);
let expected_output = 1724861211;
assert_eq!(output, expected_output);

let input = vec![0xda, 0xda, 0xa1, 0x4b];
let output = decode_leb128(&input);
let expected_output = 157838682;
assert_eq!(output, expected_output);
}
#[test]
fn test_decode_unstaking_response_data() {
let data = Binary::from(vec![
0x0a, 0x0b, 0x08, 0x9b, 0x96, 0xbd, 0xb6, 0x06, 0x10, 0xda, 0xda, 0xa1, 0x4b,
]);
let (sec, nsec) = decode_unstaking_response_data(data);

let expected_output = (1724861211, 157838682);
assert_eq!((sec, nsec), expected_output);

let data = Binary::from(vec![
0x0a, 0x0c, 0x08, 0xd1, 0xfb, 0xc2, 0xb6, 0x06, 0x10, 0xb8, 0xe3, 0xdd, 0xed, 0x02,
]);
let (sec, nsec) = decode_unstaking_response_data(data);

let expected_output = (1724956113, 766996920);
assert_eq!((sec, nsec), expected_output);
}
Loading
Loading