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/validator staking #565

Merged
merged 30 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
327376a
feat: updated withdraw fund logic to withdraw the whole balance
cowboy0015 Jul 8, 2024
57e5f0b
fix: fixed mock
cowboy0015 Jul 8, 2024
da25b96
fix: fixed linting error
cowboy0015 Jul 8, 2024
bc04323
Merge branch 'development' into feat/withdraw_funds_as_emergency
cowboy0015 Aug 8, 2024
d19f676
refactor: refactoring testing framework
cowboy0015 Aug 9, 2024
acec7a5
add: added e2e test package
cowboy0015 Aug 12, 2024
0817383
refactor: refactored validator staking test with new test package
cowboy0015 Aug 12, 2024
3ca8ec3
migrate: migrated from starship to loom
cowboy0015 Aug 21, 2024
cf70f4c
test: testing validator_staking -> unstake function
cowboy0015 Aug 23, 2024
a63a534
fix: fixed bug in unstaking response handler
cowboy0015 Aug 29, 2024
37ea2b0
fix: fixed unstaking logic|
cowboy0015 Aug 30, 2024
c019e60
Merge branch 'feat/validator-staking' into feat/validator-staking-uns…
cowboy0015 Aug 30, 2024
d96927a
test: test case for normal workflow updated
cowboy0015 Aug 30, 2024
8b1f995
fix: fixed linting err
cowboy0015 Aug 30, 2024
5e207a1
add: implemented update default validator functionality
cowboy0015 Aug 30, 2024
31c8687
fix: updated unstaking queue manamgement logic
cowboy0015 Sep 3, 2024
7dcf9dd
refactor: removed starship related bash files
cowboy0015 Sep 6, 2024
93126c4
Merge pull request #551 from andromedaprotocol/feat/validator-staking…
cowboy0015 Sep 6, 2024
5e63943
Merge branch 'feat/validator-staking' into feat/validator-staking-upd…
cowboy0015 Sep 6, 2024
8075597
fix: fixed claim rewards submessage handler
cowboy0015 Sep 6, 2024
129f11a
Merge pull request #558 from andromedaprotocol/feat/validator-staking…
cowboy0015 Sep 9, 2024
c96c74a
refactor: removed recipient from claim operation for consistency
cowboy0015 Sep 11, 2024
0515bb1
test: updated integration test for validator staking
cowboy0015 Sep 11, 2024
8156b91
fix: fixed linting error:
cowboy0015 Sep 11, 2024
0da0a10
Merge pull request #559 from andromedaprotocol/feat/validator-staking…
cowboy0015 Sep 16, 2024
132c410
merge: merged with development branch
cowboy0015 Sep 16, 2024
9019726
update: updated version from 0.2.1 to 0.2.2
cowboy0015 Sep 16, 2024
4873941
fix: fixed shunting ado
cowboy0015 Sep 17, 2024
1f08f72
update: updated changelog and shunting ado version
cowboy0015 Sep 17, 2024
adde024
fix: removed unnecessary unwrap operation from parse_params function
cowboy0015 Sep 18, 2024
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
172 changes: 101 additions & 71 deletions contracts/finance/andromeda-validator-staking/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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, SubMsg, Timestamp, Uint128,
};
use cw2::set_contract_version;

Expand All @@ -29,6 +29,7 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[EnumRepr(type = "u64")]
pub enum ReplyId {
ValidatorUnstake = 201,
SetWithdrawAddress = 202,
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down Expand Up @@ -80,11 +81,13 @@ pub fn handle_execute(ctx: ExecuteContext, msg: ExecuteMsg) -> Result<Response,
match msg {
ExecuteMsg::Stake { validator } => execute_stake(ctx, validator),
ExecuteMsg::Unstake { validator, amount } => execute_unstake(ctx, validator, amount),
ExecuteMsg::Claim {
validator,
recipient,
} => execute_claim(ctx, validator, recipient),
ExecuteMsg::WithdrawFunds {} => execute_withdraw_fund(ctx),
ExecuteMsg::Claim { validator } => execute_claim(ctx, validator),
ExecuteMsg::WithdrawFunds { denom, recipient } => {
execute_withdraw_fund(ctx, denom, recipient)
}
ExecuteMsg::UpdateDefaultValidator { validator } => {
execute_update_default_validator(ctx, validator)
}

_ => 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 All @@ -199,11 +212,7 @@ fn execute_unstake(
Ok(res)
}

fn execute_claim(
ctx: ExecuteContext,
validator: Option<Addr>,
recipient: Option<AndrAddr>,
) -> Result<Response, ContractError> {
fn execute_claim(ctx: ExecuteContext, validator: Option<Addr>) -> Result<Response, ContractError> {
let ExecuteContext {
deps, info, env, ..
} = ctx;
Expand All @@ -214,15 +223,9 @@ fn execute_claim(
// Check if the validator is valid before unstaking
is_validator(&deps, &validator)?;

let recipient_address = if let Some(ref recipient) = recipient {
recipient.get_raw_address(&deps.as_ref())?
} else {
info.sender
};

// Ensure recipient is the contract owner
// Ensure msg sender is the contract owner
ensure!(
ADOContract::default().is_contract_owner(deps.storage, recipient_address.as_str())?,
ADOContract::default().is_contract_owner(deps.storage, info.sender.as_str())?,
ContractError::Unauthorized {}
);

Expand All @@ -244,20 +247,20 @@ fn execute_claim(
);

let res = Response::new()
.add_message(DistributionMsg::SetWithdrawAddress {
address: recipient_address.to_string(),
})
.add_message(DistributionMsg::WithdrawDelegatorReward {
validator: validator.to_string(),
})
.add_attribute("action", "validator-claim-reward")
.add_attribute("recipient", recipient_address)
.add_attribute("validator", validator.to_string());

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 +271,61 @@ 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)?;

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)
}

fn execute_update_default_validator(
ctx: ExecuteContext,
validator: Addr,
) -> Result<Response, ContractError> {
let ExecuteContext { deps, info, .. } = ctx;

ensure!(
ADOContract::default().is_contract_owner(deps.storage, info.sender.as_str())?,
ContractError::Unauthorized {}
);

// Check if the validator is valid before setting to default validator
is_validator(&deps, &validator)?;

DEFAULT_VALIDATOR.save(deps.storage, &validator)?;

let res = Response::new()
.add_attribute("action", "update-default-validator")
.add_attribute("default_validator", validator.into_string());

Ok(res)
}
Expand All @@ -321,44 +350,45 @@ 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)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
if msg.result.is_err() {
return Err(ContractError::Std(StdError::generic_err(
msg.result.unwrap_err(),
)));
}
match ReplyId::from_repr(msg.id) {
Some(ReplyId::ValidatorUnstake) => on_validator_unstake(deps, msg),
_ => Ok(Response::default()),
}
}

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;
32 changes: 21 additions & 11 deletions contracts/finance/andromeda-validator-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,23 @@ impl MockValidatorStaking {
app: &mut MockApp,
sender: Addr,
validator: Option<Addr>,
recipient: Option<AndrAddr>,
) -> ExecuteResult {
let msg = mock_execute_claim_reward(validator, recipient);
let msg = mock_execute_claim_reward(validator);
self.execute(app, &msg, sender, &[])
}

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, &[])
}

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

Expand Down Expand Up @@ -99,18 +108,19 @@ pub fn mock_execute_unstake(validator: Option<Addr>, amount: Option<Uint128>) ->
ExecuteMsg::Unstake { validator, amount }
}

pub fn mock_execute_claim_reward(
validator: Option<Addr>,
pub fn mock_execute_claim_reward(validator: Option<Addr>) -> ExecuteMsg {
ExecuteMsg::Claim { validator }
}

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

pub fn mock_execute_withdraw_fund() -> ExecuteMsg {
ExecuteMsg::WithdrawFunds {}
pub fn mock_execute_update_default_validator(validator: Addr) -> ExecuteMsg {
ExecuteMsg::UpdateDefaultValidator { validator }
}

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");
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ use crate::{
testing::mock_querier::{mock_dependencies_custom, DEFAULT_VALIDATOR, VALID_VALIDATOR},
};

use andromeda_std::{
amp::AndrAddr, error::ContractError, testing::mock_querier::MOCK_KERNEL_CONTRACT,
};
use andromeda_std::{error::ContractError, testing::mock_querier::MOCK_KERNEL_CONTRACT};
use cosmwasm_std::{
coin,
testing::{mock_env, mock_info},
Expand All @@ -15,6 +13,7 @@ use cosmwasm_std::{
use andromeda_finance::validator_staking::{ExecuteMsg, InstantiateMsg};

const OWNER: &str = "owner";
const ANYONE: &str = "anyone";

fn init(deps: DepsMut, default_validator: Addr) -> Result<Response, ContractError> {
let msg = InstantiateMsg {
Expand Down Expand Up @@ -168,10 +167,9 @@ fn test_unauthorized_claim() {

let msg = ExecuteMsg::Claim {
validator: Some(valid_validator.clone()),
recipient: Some(AndrAddr::from_string("other")),
};

let info = mock_info(OWNER, &[coin(100, "uandr")]);
let info = mock_info(ANYONE, &[coin(100, "uandr")]);
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(res, ContractError::Unauthorized {});
}
Loading
Loading