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

RBAC Permissions #12

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open

RBAC Permissions #12

wants to merge 38 commits into from

Conversation

bonedaddy
Copy link
Collaborator

@bonedaddy bonedaddy commented May 17, 2024

Changes

  • Updated storage variables
  • Added new QueryMsg variants + handlers + tests
  • Added RBAC roles + role evaluation functions + tests
  • Added access control check to ExecuteMsg handler

Questions

  • When editing an existing quota, how should the stored channel_value be handled? For now I'm copying the value when editing the quota as seen here

Notes

  • For all functions in query.rs I changed the function signature to accept &dyn Storage trait instead of the full Deps object
  • A lot of the functions which used a DepsMut as the input have been changed to take in &mut DepsMut, this was done due to many of the functions that are used in sequence each needing to have DepsMut provided, which is not a cloneable type

@bonedaddy bonedaddy marked this pull request as draft May 17, 2024 06:47
bonedaddy added 22 commits May 16, 2024 23:59
this is no longer required as authorization for invoking contract functions is done via RBAC now
Add new QueryMsg variant to return queued messages
version specific migrations, as well as updating the contract version in storage
@cfkelly18
Copy link

initial review

x/ibc-rate-limit/contracts/rate-limiter/src/msg.rs Outdated Show resolved Hide resolved
Comment on lines +51 to +61
/// The balance of a flow is how much absolute value for the denom has moved
/// through the channel before period_end. It returns a tuple of
/// (balance_in, balance_out) where balance_in in is how much has been
/// transferred into the flow, and balance_out is how much value transferred
/// out.
pub fn balance(&self) -> (Uint256, Uint256) {
(
self.inflow.saturating_sub(self.outflow),
self.outflow.saturating_sub(self.inflow),
)
}

Choose a reason for hiding this comment

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

[question]
im a little confused by this function, as it wouldnt show the absolute value if the saturating sub results in 0 right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes I think that is correct, however this is from the original rate limits implementation so I'm not entirely sure what the intention is although I suspect it's meant as a way to prevent a panic from an overflow happening 🤔

x/ibc-rate-limit/contracts/rate-limiter/src/state/path.rs Outdated Show resolved Hide resolved
Comment on lines 31 to 38
/// Sets a timelock delay for `signer` of `hours`
pub fn set_timelock_delay(
deps: &mut DepsMut,
signer: String,
hours: u64
) -> Result<(), ContractError> {
Ok(TIMELOCK_DELAY.save(deps.storage, signer, &hours)?)
}

Choose a reason for hiding this comment

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

[add validations]

  1. I know that this is coming from a privileged address, and it also can just simply be reset if it was incorrectly set (but this would require a vote and cots time depending on the dao setup for that role)

Maybe enforce a config level max? and also make sure its not 0?

  1. Signer should be validated to be a valid address

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The validations take place within the execute function.

Choose a reason for hiding this comment

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

sorry I meant for msg.signer specifically. As this is just a string param passed into the msg. Maybe im missing something?

Comment on lines +106 to +124
Some(mut limits) => {
limits.iter_mut().for_each(|limit| {
if limit.quota.name.eq(&quota.name) {
// TODO: is this the current way of handling channel_value when editing the quota?

// cache the current channel_value
let channel_value = limit.quota.channel_value;
// update the quota
limit.quota = From::from(&quota);
// copy the channel_value
limit.quota.channel_value = channel_value;
}
});
Ok(limits)
}
}
})?;
Ok(())
}

Choose a reason for hiding this comment

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

Responding to question -

is this the current way of handling channel_value when editing the quota

Seems good to me; the one thing we need to consider is a situation to where a quota is set to a value that is lower than the current channel value.

But in this scenerio I think it would behave as expected, it would just rate limit subsequent transfers

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this would be a good question to ask the osmosis developers, but my assumption is that it would be good to keep the channel value for analytical purposes / ensuring the value correctly tracks the amount of value transferred in a given period.

Choose a reason for hiding this comment

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

yeah, we should keep the channel value. Otherwise we would be resetting the quota. The value will be updated on the next request anyway.

@@ -108,6 +89,40 @@ pub fn try_reset_path_quota(
.add_attribute("channel_id", channel_id))
}

pub fn edit_path_quota(

Choose a reason for hiding this comment

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

Maybe we could emit some more attributes from this fn? I think its fine either way though

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think more attributes is a good idea, just not sure what to add. Perhaps the parameters that were given when editing the quota?

x/ibc-rate-limit/contracts/rate-limiter/src/rbac.rs Outdated Show resolved Hide resolved
env.block.time,
),
// check to see if special permissions are required to invoke the message, and that the sender has the required permissions
crate::rbac::can_invoke_message(&deps, &info, &msg)?;
Copy link
Member

Choose a reason for hiding this comment

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

should we import the method above for clarity, like in other cases

Copy link
Collaborator Author

@bonedaddy bonedaddy May 23, 2024

Choose a reason for hiding this comment

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

Yea good idea (fixed in 3f3b87e)

let contract_version = get_contract_version(deps.storage)?;

// check contract version, and apply version specific migrations
if contract_version.version.eq("0.1.0") {
Copy link
Member

Choose a reason for hiding this comment

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

is it good practice to hard-code here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not entirely certain to be honest, I wasn't able to find much documentation on best practices for contract migrations.

I think hard-coding is required as when a migration would need to be done, the contract version variable embedded into the contract would reference the version of the new contract as it's set during compilation.

Choose a reason for hiding this comment

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

I think this is fine. We only have one previous version to migrate from

Some(mut limits) => {
limits.iter_mut().for_each(|limit| {
if limit.quota.name.eq(&quota.name) {
// TODO: is this the current way of handling channel_value when editing the quota?
Copy link
Member

Choose a reason for hiding this comment

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

is the TODO solved?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sort of, this is my reply to Colin's review about this todo but ultimatley I think we need Osmosis' team to clarify.

@cfkelly18
Copy link

LGTM

Comment on lines 19 to 60
pub fn process_message_queue(
deps: &mut DepsMut,
env: &Env,
count: Option<u64>,
message_ids: Option<Vec<String>>,
) -> Result<Response, ContractError> {
let mut response = Response::new();
if let Some(message_ids) = message_ids {
let messages = pop_messages(deps.storage, &message_ids)?;
for mut message in messages {
// message_id isn't needed for message processing
// so avoid a clone by taking the message_id and replacing with empty string
let message_id = std::mem::take(&mut message.message_id);
if let Err(err) = try_process_message(deps, env, message) {
response = response.add_attribute(message_id, err.to_string());
} else {
response = response.add_attribute(message_id, "ok");
}
}
return Ok(response);
}
let Some(count) = count else {
return Err(ContractError::InvalidParameters("both count and message_ids are None".to_string()))
};
let queue_len = MESSAGE_QUEUE.len(deps.storage)? as usize;

for idx in 0..queue_len {
if idx + 1 > count {
if idx + 1 > count as usize {
break;
}
if let Some(message) = MESSAGE_QUEUE.pop_front(deps.storage)? {
// compute the minimum time at which the message is unlocked
let min_unlock = message
.submitted_at
.plus_seconds(message.timelock_delay * 60 * 60);
// check to see if the timelock delay has passed, which we need to first convert from hours into seconds
if env.block.time.ge(&min_unlock)
{
crate::contract::match_execute(deps, &env, message.message)?;
if let Some(mut message) = MESSAGE_QUEUE.pop_front(deps.storage)? {
let message_id = std::mem::take(&mut message.message_id);
if let Err(err) = try_process_message(deps, env, message) {
response = response.add_attribute(message_id, err.to_string());
} else {
MESSAGE_QUEUE.push_back(deps.storage, &message)?;
response = response.add_attribute(message_id, "ok");
}
}
}
Ok(())
Ok(response)
}

Choose a reason for hiding this comment

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

two thoughts here:

  • we may want to store the errored messages in a separate item mapping rather than removing them and emitting an attribute. this way we can then allow a query to be made to query the failed message ids. This would help debugging failed messages, and also allow for a corrective message to be made. Overall this would just provide more visability
    very very theoretical edge case: im not concerned about this in the real world
  • One downside to this FIFO pop queue approach, is that anybody can call the function and effectively push any specific message id to the back of the queue since the order is not preserved. For example if osmosis has a bot calling process messages count {10} every hour, an adversary could identify an important message id X and then call process messages with the id x when the queue time us T-1. This would push it to the back and delay the message processing if the queue is large or intentionally spammed. But this is also mostly mitigated with the ability to now process just a specific id. I dont see a need to fix this, but I wanted to raise the idea in case it conjured up some security considerations

@bonedaddy bonedaddy marked this pull request as ready for review May 28, 2024 18:38
@bonedaddy bonedaddy changed the title [WIP] RBAC Permissions RBAC Permissions May 28, 2024
Copy link

@nicolaslara nicolaslara left a comment

Choose a reason for hiding this comment

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

Did a pretty quick review, but this generally looks good to me. It would be great if you can make the PR to the osmosis repo so others from the team can review it.

We should also test this on testnet to see how it behaves and try to catch any potential errors.

@bonedaddy
Copy link
Collaborator Author

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.

4 participants