Skip to content

Commit

Permalink
refactor: first implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kupermind committed Oct 22, 2024
1 parent aa04650 commit 6199f79
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 27 deletions.
197 changes: 172 additions & 25 deletions contracts/contribute/ContributeServiceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ interface IContributors {
/// @param socialId Social id.
/// @param serviceId Service Id.
/// @param multisig Service multisig address.
function setServiceInfoForId(uint256 socialId, uint256 serviceId, address multisig) external;
/// @param stakingInstance Staking instance address.
/// @param serviceOwner Service owner.
function setServiceInfoForId(
uint256 socialId,
uint256 serviceId,
address multisig,
address stakingInstance,
address serviceOwner
) external;

/// @dev Gets service info corresponding to a specified social Id.
/// @param socialId Social Id.
/// @return service Id Corresponding service Id.
/// @return serviceId Corresponding service Id.
/// @return multisig Corresponding service multisig.
function mapSocialIdServiceInfo(uint256 socialId) external view returns (uint256 serviceId, address multisig);
/// @return stakingInstance Staking instance address.
/// @return serviceOwner Service owner.
function mapSocialIdServiceInfo(uint256 socialId) external view
returns (uint256 serviceId, address multisig, address stakingInstance, address serviceOwner);
}

// Staking interface
Expand All @@ -35,10 +46,30 @@ interface IStaking {
/// @dev Gets the service threshold.
/// @return Threshold.
function threshold() external view returns (uint256);

/// @dev Stakes the service.
/// @param serviceId Service Id.
function stake(uint256 serviceId) external;

/// @dev Unstakes the service with collected reward, if available.
/// @param serviceId Service Id.
/// @return reward Staking reward.
function unstake(uint256 serviceId) external returns (uint256);

/// @dev Claims rewards for the service without an additional checkpoint call.
/// @param serviceId Service Id.
/// @return Staking reward.
function claim(uint256 serviceId) external returns (uint256);
}

// Token interface
interface IToken {
/// @dev Transfers the token amount.
/// @param to Address to transfer to.
/// @param amount The amount to transfer.
/// @return True if the function execution is successful.
function transfer(address to, uint256 amount) external returns (bool);

/// @dev Transfers the token amount that was previously approved up until the maximum allowance.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
Expand All @@ -59,21 +90,41 @@ error ZeroAddress();
/// @dev Zero value.
error ZeroValue();

/// @dev Service is already created for the contributor.
/// @dev Service is already created and staked for the contributor.
/// @param socialId Social Id.
/// @param serviceId Service Id.
/// @param multisig Multisig address.
error ServiceAlreadyCreated(uint256 serviceId, address multisig);
error ServiceAlreadyStaked(uint256 socialId, uint256 serviceId, address multisig);

/// @dev Wrong staking instance.
/// @param stakingInstance Staking instance address.
error WrongStakingInstance(address stakingInstance);

/// @dev Service is not defined for the social Id.
/// @param socialId Social Id.
error ServiceNotDefined(uint256 socialId);

/// @dev Wrong service owner.
/// @param serviceId Service Id.
/// @param sender Sender address.
/// @param serviceOwner Actual service owner.
error ServiceOwnerOnly(uint256 serviceId, address sender, address serviceOwner);

/// @title ContributeServiceManager - Smart contract for managing services for contributors
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Tatiana Priemova - <[email protected]>
/// @author David Vilela - <[email protected]>
contract ContributeServiceManager {
event CreatedAndStaked(uint256 indexed socialId, address indexed serviceOwner, uint256 serviceId,
address indexed multisig, address stakingInstance);
event Staked(uint256 indexed socialId, address indexed serviceOwner, uint256 serviceId,
address indexed multisig, address stakingInstance);
event Unstaked(uint256 indexed socialId, address indexed serviceOwner, uint256 serviceId,
address indexed multisig, address stakingInstance);
event Claimed(uint256 indexed socialId, address indexed serviceOwner, uint256 serviceId,
address indexed multisig, address stakingInstance);

// Contribute agent Id
uint256 public constant AGENT_ID = 6;
// Contributor service config hash mock
Expand All @@ -82,15 +133,20 @@ contract ContributeServiceManager {
address public immutable contributorsProxy;
// Service manager contract address
address public immutable serviceManager;
// Service registry address
address public immutable serviceRegistry;
// Service registry token utility address
address public immutable serviceRegistryTokenUtility;
// Safe multisig processing contract address
address public immutable safeMultisig;
// Safe fallback handler
address public immutable fallbackHandler;

/// @dev StakingNativeToken initialization.
/// @param _contributorsProxy Contributors proxy contract address.
/// @dev ContributeServiceManager constructor.
/// @param _contributorsProxy Contributors proxy address.
/// @param _serviceManager Service manager address.
/// @param _safeMultisig Safe multisig address.
/// @param _fallbackHandler Multisig fallback handler address.
constructor(address _contributorsProxy, address _serviceManager, address _safeMultisig, address _fallbackHandler) {
// Check the zero address
if (_contributorsProxy == address(0) || _serviceManager == address(0) || _safeMultisig == address(0) ||
Expand All @@ -102,19 +158,52 @@ contract ContributeServiceManager {
serviceManager = _serviceManager;
safeMultisig = _safeMultisig;
fallbackHandler = _fallbackHandler;
serviceRegistry = IService(serviceManager).serviceRegistry();
serviceRegistryTokenUtility = IService(serviceManager).serviceRegistryTokenUtility();
}

function _createAndDeploy(
address token,
uint256 minStakingDeposit,
uint256 numAgentInstances,
uint256 threshold
) internal returns (uint256 serviceId, address multisig) {
// Set agent params
IService.AgentParams[] memory agentParams = new IService.AgentParams[](1);
agentParams[0] = IService.AgentParams(uint32(numAgentInstances), uint96(minStakingDeposit));

// Set agent Ids
uint32[] memory agentIds = new uint32[](1);
agentIds[0] = uint32(AGENT_ID);

// Set agent instances as [msg.sender]
address[] memory instances = new address[](1);
instances[0] = msg.sender;

// Create a service owned by this contract
serviceId = IService(serviceManager).create(address(this), token, CONFIG_HASH, agentIds,
agentParams, uint32(threshold));

// Activate registration (1 wei as a deposit wrapper)
IService(serviceManager).activateRegistration{value: 1}(serviceId);

// Register msg.sender as an agent instance (numAgentInstances wei as a bond wrapper)
IService(serviceManager).registerAgents{value: numAgentInstances}(serviceId, instances, agentIds);

// Deploy the service
// TODO: fix the data
multisig = IService(serviceManager).deploy(serviceId, safeMultisig, "0x");
}

/// @dev Creates and deploys a service for the contributor, and stakes it with a specified staking contract.
/// @notice The service cannot be registered again if it is currently staked.
/// @param socialId Contributor social Id.
/// @param stakingInstance Contribute staking instance address.
function createAndStake(uint256 socialId, address stakingInstance) external payable {
// Check for existing service corresponding to the social Id
(uint256 serviceId, address multisig) = IContributors(contributorsProxy).mapSocialIdServiceInfo(socialId);

(uint256 serviceId, address multisig, , ) = IContributors(contributorsProxy).mapSocialIdServiceInfo(socialId);
if (serviceId > 0) {
revert ServiceAlreadyCreated(serviceId, multisig);
revert ServiceAlreadyStaked(socialId, serviceId, multisig);
}

// Get the token info from the staking contract
Expand Down Expand Up @@ -155,30 +244,88 @@ contract ContributeServiceManager {
// Approve token for the serviceRegistryTokenUtility contract
IToken(token).approve(serviceRegistryTokenUtility, totalBond);

// Create a service owned by this contract
serviceId = IService(serviceManager).create(address(this), token, CONFIG_HASH, [AGENT_ID],
IService.AgentParams(numAgentInstances, minStakingDeposit), threshold);
// Create and deploy service
(serviceId, multisig) = _createAndDeploy(token, minStakingDeposit, numAgentInstances, threshold);

// Activate registration (1 wei as a deposit wrapper)
IService(serviceManager).activateRegistration{value: 1}(serviceId);

// Register msg.sender as an agent instance (numAgentInstances wei as a bond wrapper)
IService(serviceManager).registerAgents{value: numAgentInstances}(serviceId, [msg.sender], [AGENT_ID]);
// Stake the service
_stake(socialId, serviceId, multisig, stakingInstance);

// Deploy the service
// TODO: fix the data
IService(serviceManager).deploy(serviceId, safeMultisig, "0x");
emit CreatedAndStaked(socialId, msg.sender, serviceId, multisig, stakingInstance);
}

function _stake(uint256 socialId, uint256 serviceId, address multisig, address stakingInstance) public {
// Add the service into its social Id corresponding record
IContributors(contributorsProxy).setServiceInfoForId(socialId, serviceId, multisig);
// stake(stakingInstance);
IContributors(contributorsProxy).setServiceInfoForId(socialId, serviceId, multisig, stakingInstance, msg.sender);

// Stake the service
IStaking(stakingInstance).stake(serviceId);
}

/// @dev Stakes the already deployed service.
function stake(uint256 socialId, uint256 serviceId, address stakingInstance) public {
// Check for existing service corresponding to the social Id
(uint256 serviceIdCheck, address multisig, , ) = IContributors(contributorsProxy).mapSocialIdServiceInfo(socialId);
if (serviceIdCheck > 0) {
revert ServiceAlreadyStaked(socialId, serviceIdCheck, multisig);
}

// Transfer the service NFT
IToken(serviceRegistry).transferFrom(msg.sender, address(this), serviceId);

// Approve service NFT for the staking instance
IToken(serviceRegistry).approve(stakingInstance, serviceId);

// Get the service multisig
(, multisig, , , , , ) = IService(serviceRegistry).mapServices(serviceId);

// Stake the service
_stake(socialId, serviceId, multisig, stakingInstance);

emit Staked(socialId, msg.sender, serviceId, multisig, stakingInstance);
}

function stake(uint256 serviceId, address stakingInstance) public {
function unstake(uint256 socialId) external {
// Check for existing service corresponding to the social Id
(uint256 serviceId, address multisig, address stakingInstance, address serviceOwner) =
IContributors(contributorsProxy).mapSocialIdServiceInfo(socialId);
if (serviceId == 0) {
revert ServiceNotDefined(socialId);
}

// Check for service owner
if (msg.sender != serviceOwner) {
revert ServiceOwnerOnly(serviceId, msg.sender, serviceOwner);
}

// Unstake the service
IStaking(stakingInstance).unstake(serviceId);

// Transfer the service back to the original owner
IToken(serviceRegistry).transfer(serviceOwner, serviceId);

// Zero the service info: the service is out of the contribute records, however multisig activity is still valid
// If the same service is staked back, the multisig activity continues being tracked
IContributors(contributorsProxy).setServiceInfoForId(socialId, 0, address(0), address(0), address(0));

emit Unstaked(socialId, msg.sender, serviceId, multisig, stakingInstance);
}

function unstake() external {
function claim(uint256 socialId) external {
// Check for existing service corresponding to the social Id
(uint256 serviceId, address multisig, address stakingInstance, address serviceOwner) =
IContributors(contributorsProxy).mapSocialIdServiceInfo(socialId);
if (serviceId == 0) {
revert ServiceNotDefined(socialId);
}

// Check for service owner
if (msg.sender != serviceOwner) {
revert ServiceOwnerOnly(serviceId, msg.sender, serviceOwner);
}

// Claim staking rewards
IStaking(stakingInstance).claim(serviceId);

emit Claimed(socialId, msg.sender, serviceId, multisig, stakingInstance);
}
}
18 changes: 16 additions & 2 deletions contracts/contribute/Contributors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ struct ServiceInfo {
uint256 serviceId;
// Corresponding service multisig
address multisig;
// Staking instance address
address stakingInstance;
// Service owner address
address serviceOwner;
}

/// @title Contributors - Smart contract for managing contributors
Expand Down Expand Up @@ -133,12 +137,20 @@ contract Contributors {
manager = newManager;
emit ManagerUpdated(newManager);
}

/// @dev Sets service info for the social id.
/// @param socialId Social id.
/// @param serviceId Service Id.
/// @param multisig Service multisig address.
function setServiceInfoForId(uint256 socialId, uint256 serviceId, address multisig) external {
/// @param stakingInstance Staking instance address.
/// @param serviceOwner Service owner.
function setServiceInfoForId(
uint256 socialId,
uint256 serviceId,
address multisig,
address stakingInstance,
address serviceOwner
) external {
// Check for manager
if (msg.sender != manager) {
revert OnlyManager(msg.sender, manager);
Expand All @@ -148,6 +160,8 @@ contract Contributors {
ServiceInfo storage serviceInfo = mapSocialIdServiceInfo[socialId];
serviceInfo.serviceId = serviceId;
serviceInfo.multisig = multisig;
serviceInfo.stakingInstance = stakingInstance;
serviceInfo.serviceOwner = serviceOwner;

emit SetServiceInfoForId(socialId, serviceId, multisig);
}
Expand Down
23 changes: 23 additions & 0 deletions contracts/contribute/interfaces/IService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,30 @@ interface IService {
bytes memory data
) external returns (address multisig);

/// @dev Gets the serviceRegistry address.
/// @return serviceRegistry address.
function serviceRegistry() external returns (address);

/// @dev Gets the serviceRegistryTokenUtility address.
/// @return serviceRegistryTokenUtility address.
function serviceRegistryTokenUtility() external returns (address);

/// @dev Gets the service instance from the map of services.
/// @param serviceId Service Id.
/// @return securityDeposit Registration activation deposit.
/// @return multisig Service multisig address.
/// @return configHash IPFS hashes pointing to the config metadata.
/// @return threshold Agent instance signers threshold.
/// @return maxNumAgentInstances Total number of agent instances.
/// @return numAgentInstances Actual number of agent instances.
/// @return state Service state.
function mapServices(uint256 serviceId) external view returns (
uint96 securityDeposit,
address multisig,
bytes32 configHash,
uint32 threshold,
uint32 maxNumAgentInstances,
uint32 numAgentInstances,
uint8 state
);
}

0 comments on commit 6199f79

Please sign in to comment.