@@ -1208,63 +1208,74 @@ pub async fn transfer_ownership(
12081208 target_address : Address ,
12091209 new_owner : Address ,
12101210) -> Result < TransactionReceipt > {
1211- let receipt = match target_contract {
1212- Contract :: LightClient | Contract :: LightClientProxy => {
1213- tracing:: info!( %target_address, %new_owner, "Transfer LightClient ownership" ) ;
1214- let lc = LightClient :: new ( target_address, & provider) ;
1215- lc. transferOwnership ( new_owner)
1216- . send ( )
1217- . await ?
1218- . get_receipt ( )
1219- . await ?
1220- } ,
1221- Contract :: FeeContract | Contract :: FeeContractProxy => {
1222- tracing:: info!( %target_address, %new_owner, "Transfer FeeContract ownership" ) ;
1223- let fee = FeeContract :: new ( target_address, & provider) ;
1224- fee. transferOwnership ( new_owner)
1225- . send ( )
1226- . await ?
1227- . get_receipt ( )
1228- . await ?
1229- } ,
1230- Contract :: EspToken | Contract :: EspTokenProxy => {
1231- tracing:: info!( %target_address, %new_owner, "Transfer EspToken ownership" ) ;
1232- let token = EspToken :: new ( target_address, & provider) ;
1233- token
1234- . transferOwnership ( new_owner)
1235- . send ( )
1236- . await ?
1237- . get_receipt ( )
1238- . await ?
1239- } ,
1240- Contract :: StakeTable | Contract :: StakeTableProxy | Contract :: StakeTableV2 => {
1241- tracing:: info!( %target_address, %new_owner, "Transfer StakeTable ownership" ) ;
1242- let stake_table = StakeTable :: new ( target_address, & provider) ;
1243- stake_table
1244- . transferOwnership ( new_owner)
1245- . send ( )
1246- . await ?
1247- . get_receipt ( )
1248- . await ?
1249- } ,
1250- Contract :: RewardClaim | Contract :: RewardClaimProxy => {
1251- tracing:: info!( %target_address, %new_owner, "Grant RewardClaim DEFAULT_ADMIN_ROLE" ) ;
1252- let reward_claim = RewardClaim :: new ( target_address, & provider) ;
1253- let admin_role = reward_claim. DEFAULT_ADMIN_ROLE ( ) . call ( ) . await ?;
1254- reward_claim
1255- . grantRole ( admin_role, new_owner)
1256- . send ( )
1257- . await ?
1258- . get_receipt ( )
1259- . await ?
1260- } ,
1261- _ => return Err ( anyhow ! ( "Not Ownable, can't transfer ownership!" ) ) ,
1262- } ;
1211+ // Use OwnableUpgradeable interface for all Ownable contracts
1212+ // This is more generic and maintainable than matching on each contract type
1213+ let ownable = OwnableUpgradeable :: new ( target_address, & provider) ;
1214+
1215+ // Verify the contract is actually Ownable by checking if we can read the owner
1216+ let current_owner = ownable. owner ( ) . call ( ) . await . context ( format ! (
1217+ "Contract at {target_address:#x} does not implement Ownable interface"
1218+ ) ) ?;
1219+
1220+ tracing:: info!( %target_contract, %target_address, current_owner = %current_owner, new_owner = %new_owner, "Transferring ownership of {target_contract}" ) ;
1221+
1222+ let receipt = ownable
1223+ . transferOwnership ( new_owner)
1224+ . send ( )
1225+ . await ?
1226+ . get_receipt ( )
1227+ . await ?;
1228+
12631229 let tx_hash = receipt. transaction_hash ;
12641230 tracing:: info!( %receipt. gas_used, %tx_hash, "ownership transferred" ) ;
12651231 Ok ( receipt)
12661232}
12671233
1234+ /// Grant DEFAULT_ADMIN_ROLE to a new admin for AccessControl-based contracts
1235+ /// This handles contracts like RewardClaim that use AccessControl instead of Ownable
1236+ /// TODO: create a function for pauser roles
1237+ pub async fn grant_admin_role (
1238+ provider : impl Provider ,
1239+ target_contract : Contract ,
1240+ target_address : Address ,
1241+ new_admin : Address ,
1242+ ) -> Result < TransactionReceipt > {
1243+ // Use AccessControlUpgradeable interface
1244+ let access_control = AccessControlUpgradeable :: new ( target_address, & provider) ;
1245+
1246+ // Verify the contract is actually AccessControl by checking if we can read roles
1247+ let admin_role = access_control
1248+ . DEFAULT_ADMIN_ROLE ( )
1249+ . call ( )
1250+ . await
1251+ . context ( format ! (
1252+ "Contract at {target_address:#x} does not implement AccessControl interface"
1253+ ) ) ?;
1254+
1255+ // Check if new_admin already has the role (for logging purposes)
1256+ let already_has_role = access_control. hasRole ( admin_role, new_admin) . call ( ) . await ?;
1257+
1258+ tracing:: info!(
1259+ %target_contract,
1260+ %target_address,
1261+ new_admin = %new_admin,
1262+ already_has_role = %already_has_role,
1263+ "Granting DEFAULT_ADMIN_ROLE for {target_contract}"
1264+ ) ;
1265+
1266+ // For RewardClaim, grantRole handles the revoke of the previous admin internally
1267+ let receipt = access_control
1268+ . grantRole ( admin_role, new_admin)
1269+ . send ( )
1270+ . await ?
1271+ . get_receipt ( )
1272+ . await ?;
1273+
1274+ let tx_hash = receipt. transaction_hash ;
1275+ tracing:: info!( %receipt. gas_used, %tx_hash, "admin role granted" ) ;
1276+ Ok ( receipt)
1277+ }
1278+
12681279/// helper function to decide if the contract at given address `addr` is a proxy contract
12691280pub async fn is_proxy_contract ( provider : impl Provider , addr : Address ) -> Result < bool > {
12701281 // when the implementation address is not equal to zero, it's a proxy
@@ -3468,4 +3479,115 @@ mod tests {
34683479
34693480 Ok ( ( ) )
34703481 }
3482+
3483+ #[ test_log:: test( tokio:: test) ]
3484+ async fn test_grant_admin_role_reward_claim ( ) -> Result < ( ) > {
3485+ let ( _anvil, provider, l1_client) =
3486+ ProviderBuilder :: new ( ) . connect_anvil_with_l1_client ( ) ?;
3487+
3488+ let mut contracts = Contracts :: new ( ) ;
3489+ let deployer = l1_client. get_accounts ( ) . await ?[ 0 ] ;
3490+ let new_admin = Address :: random ( ) ;
3491+
3492+ // Deploy RewardClaim
3493+ let esp_token_addr = deploy_token_proxy (
3494+ & provider,
3495+ & mut contracts,
3496+ deployer,
3497+ deployer,
3498+ U256 :: from ( 10_000_000u64 ) ,
3499+ "Test Token" ,
3500+ "TEST" ,
3501+ )
3502+ . await ?;
3503+ let lc_addr = deploy_light_client_contract ( & provider, & mut contracts, false ) . await ?;
3504+ let reward_claim_addr = deploy_reward_claim_proxy (
3505+ & provider,
3506+ & mut contracts,
3507+ esp_token_addr,
3508+ lc_addr,
3509+ deployer,
3510+ deployer, // pauser
3511+ )
3512+ . await ?;
3513+
3514+ // Verify initial admin
3515+ let reward_claim = RewardClaim :: new ( reward_claim_addr, & provider) ;
3516+ let admin_role = reward_claim. DEFAULT_ADMIN_ROLE ( ) . call ( ) . await ?;
3517+ assert ! ( reward_claim. hasRole( admin_role, deployer) . call( ) . await ?) ;
3518+ assert ! ( !reward_claim. hasRole( admin_role, new_admin) . call( ) . await ?) ;
3519+
3520+ // Grant admin role
3521+ let receipt = grant_admin_role (
3522+ & provider,
3523+ Contract :: RewardClaimProxy ,
3524+ reward_claim_addr,
3525+ new_admin,
3526+ )
3527+ . await ?;
3528+
3529+ assert ! ( receipt. inner. is_success( ) ) ;
3530+
3531+ // Verify new admin has role and old admin doesn't
3532+ assert ! ( reward_claim. hasRole( admin_role, new_admin) . call( ) . await ?) ;
3533+ assert ! ( !reward_claim. hasRole( admin_role, deployer) . call( ) . await ?) ;
3534+
3535+ Ok ( ( ) )
3536+ }
3537+
3538+ #[ test_log:: test( tokio:: test) ]
3539+ async fn test_transfer_ownership_from_eoa_reward_claim_routes_to_grant_role ( ) -> Result < ( ) > {
3540+ let ( anvil, provider, l1_client) = ProviderBuilder :: new ( ) . connect_anvil_with_l1_client ( ) ?;
3541+
3542+ let mut contracts = Contracts :: new ( ) ;
3543+ let deployer = l1_client. get_accounts ( ) . await ?[ 0 ] ;
3544+ let new_admin = Address :: random ( ) ;
3545+
3546+ // Deploy RewardClaim
3547+ let esp_token_addr = deploy_token_proxy (
3548+ & provider,
3549+ & mut contracts,
3550+ deployer,
3551+ deployer,
3552+ U256 :: from ( 10_000_000u64 ) ,
3553+ "Test Token" ,
3554+ "TEST" ,
3555+ )
3556+ . await ?;
3557+ let lc_addr = deploy_light_client_contract ( & provider, & mut contracts, false ) . await ?;
3558+ let reward_claim_addr = deploy_reward_claim_proxy (
3559+ & provider,
3560+ & mut contracts,
3561+ esp_token_addr,
3562+ lc_addr,
3563+ deployer,
3564+ deployer, // pauser
3565+ )
3566+ . await ?;
3567+
3568+ // Verify initial admin
3569+ let reward_claim = RewardClaim :: new ( reward_claim_addr, & provider) ;
3570+ let admin_role = reward_claim. DEFAULT_ADMIN_ROLE ( ) . call ( ) . await ?;
3571+ assert ! ( reward_claim. hasRole( admin_role, deployer) . call( ) . await ?) ;
3572+ assert ! ( !reward_claim. hasRole( admin_role, new_admin) . call( ) . await ?) ;
3573+
3574+ use builder:: DeployerArgsBuilder ;
3575+
3576+ let mut args_builder = DeployerArgsBuilder :: default ( ) ;
3577+ args_builder
3578+ . deployer ( provider. clone ( ) )
3579+ . rpc_url ( anvil. endpoint_url ( ) )
3580+ . transfer_ownership_from_eoa ( true )
3581+ . target_contract ( "rewardclaim" . to_string ( ) )
3582+ . transfer_ownership_new_owner ( new_admin) ;
3583+ let args = args_builder. build ( ) ?;
3584+
3585+ args. transfer_ownership_from_eoa ( & mut contracts) . await ?;
3586+
3587+ // Verify new admin has role and old admin doesn't
3588+ assert ! ( reward_claim. hasRole( admin_role, new_admin) . call( ) . await ?) ;
3589+ assert ! ( !reward_claim. hasRole( admin_role, deployer) . call( ) . await ?) ;
3590+
3591+ Ok ( ( ) )
3592+ }
34713593}
0 commit comments