use alloy_primitives::{Address, B256, keccak256};
pub const VAULT_ACTIONS: &[&str] = &["manageUserBalance", "batchSwap"];
const MANAGE_USER_BALANCE_SELECTOR: [u8; 4] = [0x0e, 0x8e, 0x3e, 0x84];
const BATCH_SWAP_SELECTOR: [u8; 4] = [0x94, 0x5b, 0xce, 0xc9];
#[must_use]
pub fn vault_role_hash(vault: Address, selector: [u8; 4]) -> B256 {
let mut buf = Vec::with_capacity(20 + 4);
buf.extend_from_slice(vault.as_slice());
buf.extend_from_slice(&selector);
keccak256(&buf)
}
#[must_use]
pub fn required_vault_role_selectors() -> Vec<[u8; 4]> {
vec![MANAGE_USER_BALANCE_SELECTOR, BATCH_SWAP_SELECTOR]
}
#[must_use]
pub fn grant_role_calldata(role: B256, account: Address) -> Vec<u8> {
let selector = &keccak256("grantRole(bytes32,address)")[..4];
let mut buf = Vec::with_capacity(68);
buf.extend_from_slice(selector);
buf.extend_from_slice(role.as_slice());
buf.extend_from_slice(&abi_address(account));
buf
}
#[must_use]
pub fn revoke_role_calldata(role: B256, account: Address) -> Vec<u8> {
let selector = &keccak256("revokeRole(bytes32,address)")[..4];
let mut buf = Vec::with_capacity(68);
buf.extend_from_slice(selector);
buf.extend_from_slice(role.as_slice());
buf.extend_from_slice(&abi_address(account));
buf
}
#[must_use]
#[allow(clippy::type_complexity, reason = "return type matches domain contract encoding")]
pub fn required_vault_role_calls(
vault: Address,
authorizer: Address,
account: Address,
) -> Vec<(Address, Vec<u8>)> {
required_vault_role_selectors()
.into_iter()
.map(|selector| {
let role = vault_role_hash(vault, selector);
let calldata = grant_role_calldata(role, account);
(authorizer, calldata)
})
.collect()
}
fn abi_address(a: Address) -> [u8; 32] {
let mut buf = [0u8; 32];
buf[12..].copy_from_slice(a.as_slice());
buf
}
#[cfg(test)]
mod tests {
use alloy_primitives::address;
use super::*;
#[test]
fn vault_actions_list() {
assert_eq!(VAULT_ACTIONS.len(), 2);
assert_eq!(VAULT_ACTIONS[0], "manageUserBalance");
assert_eq!(VAULT_ACTIONS[1], "batchSwap");
}
#[test]
fn required_selectors_count() {
let selectors = required_vault_role_selectors();
assert_eq!(selectors.len(), 2);
}
#[test]
fn vault_role_hash_deterministic() {
let vault = address!("BA12222222228d8Ba445958a75a0704d566BF2C8");
let h1 = vault_role_hash(vault, MANAGE_USER_BALANCE_SELECTOR);
let h2 = vault_role_hash(vault, MANAGE_USER_BALANCE_SELECTOR);
assert_eq!(h1, h2);
assert_ne!(h1, B256::ZERO);
}
#[test]
fn vault_role_hash_differs_by_selector() {
let vault = address!("BA12222222228d8Ba445958a75a0704d566BF2C8");
let h1 = vault_role_hash(vault, MANAGE_USER_BALANCE_SELECTOR);
let h2 = vault_role_hash(vault, BATCH_SWAP_SELECTOR);
assert_ne!(h1, h2);
}
#[test]
fn vault_role_hash_differs_by_vault() {
let v1 = address!("1111111111111111111111111111111111111111");
let v2 = address!("2222222222222222222222222222222222222222");
let h1 = vault_role_hash(v1, MANAGE_USER_BALANCE_SELECTOR);
let h2 = vault_role_hash(v2, MANAGE_USER_BALANCE_SELECTOR);
assert_ne!(h1, h2);
}
#[test]
fn grant_role_calldata_format() {
let role = B256::ZERO;
let account = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let cd = grant_role_calldata(role, account);
assert_eq!(cd.len(), 68);
let expected_sel = &keccak256("grantRole(bytes32,address)")[..4];
assert_eq!(&cd[..4], expected_sel);
assert_eq!(&cd[4..36], role.as_slice());
assert_eq!(&cd[48..68], account.as_slice());
}
#[test]
fn revoke_role_calldata_format() {
let role = B256::ZERO;
let account = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
let cd = revoke_role_calldata(role, account);
assert_eq!(cd.len(), 68);
let expected_sel = &keccak256("revokeRole(bytes32,address)")[..4];
assert_eq!(&cd[..4], expected_sel);
assert_eq!(&cd[4..36], role.as_slice());
assert_eq!(&cd[48..68], account.as_slice());
}
#[test]
fn grant_and_revoke_differ() {
let role = B256::ZERO;
let account = Address::ZERO;
let grant = grant_role_calldata(role, account);
let revoke = revoke_role_calldata(role, account);
assert_ne!(&grant[..4], &revoke[..4]);
assert_eq!(&grant[4..], &revoke[4..]);
}
#[test]
fn required_vault_role_calls_produces_correct_count() {
let vault = address!("BA12222222228d8Ba445958a75a0704d566BF2C8");
let authorizer = address!("1111111111111111111111111111111111111111");
let account = address!("2222222222222222222222222222222222222222");
let calls = required_vault_role_calls(vault, authorizer, account);
assert_eq!(calls.len(), 2);
for (target, data) in &calls {
assert_eq!(*target, authorizer);
assert_eq!(data.len(), 68);
}
}
#[test]
fn required_vault_role_calls_embeds_account() {
let vault = address!("BA12222222228d8Ba445958a75a0704d566BF2C8");
let authorizer = address!("1111111111111111111111111111111111111111");
let account = address!("2222222222222222222222222222222222222222");
let calls = required_vault_role_calls(vault, authorizer, account);
for (_, data) in &calls {
assert_eq!(&data[48..68], account.as_slice());
}
}
#[test]
fn required_vault_role_calls_uses_grant_selector() {
let vault = Address::ZERO;
let authorizer = Address::ZERO;
let account = Address::ZERO;
let calls = required_vault_role_calls(vault, authorizer, account);
let expected_sel = &keccak256("grantRole(bytes32,address)")[..4];
for (_, data) in &calls {
assert_eq!(&data[..4], expected_sel);
}
}
}