-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
RBAC Permissions #12
Conversation
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
initial review |
/// 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), | ||
) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🤔
/// 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)?) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[add validations]
- 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?
- Signer should be validated to be a valid address
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
Some(mut limits) => { | ||
limits.iter_mut().for_each(|limit| { | ||
if limit.quota.name.eq("a.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("a); | ||
// copy the channel_value | ||
limit.quota.channel_value = channel_value; | ||
} | ||
}); | ||
Ok(limits) | ||
} | ||
} | ||
})?; | ||
Ok(()) | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
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)?; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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") { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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("a.name) { | ||
// TODO: is this the current way of handling channel_value when editing the quota? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the TODO solved?
There was a problem hiding this comment.
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.
LGTM |
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) | ||
} | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this 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.
Changes
QueryMsg
variants + handlers + testsExecuteMsg
handlerQuestions
channel_value
be handled? For now I'm copying the value when editing the quota as seen hereNotes
query.rs
I changed the function signature to accept&dyn Storage
trait instead of the fullDeps
objectDepsMut
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 haveDepsMut
provided, which is not a cloneable type