Skip to content

Latest commit

 

History

History
executable file
·
219 lines (165 loc) · 11.2 KB

File metadata and controls

executable file
·
219 lines (165 loc) · 11.2 KB

stOLAS — Agent Workflow

This file provides agent workflow description section.

Workflow Diagram

graph LR
  StartRound
  CheckAnyWorkRound
  WaitingRound

  StartRound-->|DONE|CheckAnyWorkRound
  CheckAnyWorkRound-->|CLAIM_BRIDGED_TOKEN|ClaimBridgedTokensRound
  CheckAnyWorkRound-->|FINALIZE_BRIDGED_TOKEN|FinalizeBridgedTokensRound
  CheckAnyWorkRound-->|CALL_REDEEM|RedeemRound
  CheckAnyWorkRound-->|CLAIM_REWARDS|ClaimRewardTokensRound
  CheckAnyWorkRound-->|CALL_CHECKPOINTS|CheckpointRound 
  CheckAnyWorkRound-->|TRIGGER_L2_TO_L1|TriggerL2ToL1BridgeRound
  CheckAnyWorkRound-->|NO_WORK|WaitingRound
  

  ClaimRewardTokensRound-->|DONE|CheckAnyWorkRound
  FinalizeBridgedTokensRound-->|DONE|CheckAnyWorkRound
  ClaimBridgedTokensRound-->|DONE|CheckAnyWorkRound
  TriggerL2ToL1BridgeRound-->|DONE|CheckAnyWorkRound
  CheckpointRound-->|DONE|CheckAnyWorkRound
  RedeemRound-->|DONE|CheckAnyWorkRound
  WaitingRound-->|DONE|CheckAnyWorkRound
Loading

Agent Workflow — Step by Step

Claim Bridged Tokens (L2 -> L1)

Agents are going to interact with relevant L1 bridge contracts in order to finalize fund transfers from L2 to L1. Each native bridge is specific to its fund transfer finalization times and routines.

Gnosis Bridge

In order to finalize token transfer on L1, the executeSignatures() function needs to be called on AMB (Foreign) contract with the following parameters:

  • _data: encodedData value from UserRequestForSignature() event from AMB (Home) contract;
  • _signatures: from the return value of getSignatures() method.

Read full Gnosis guide on how to call execute signatures here.

Base bridge

In order to finalize token transfer on L1, the relayMessage() function needs to be called on L1CrossDomainMessenger contract. However, there is a script that facilitates a required sequence of calls for bridging assets from L2 to L1.

It is advised to use documentation and workflow provided here.

Finalize Bridged Tokens on L1

Once tokens are fully bridged on L1 in their corresponding contracts, the last step is to direct them to designated destinations. Currently, there are two contracts that require L2-L1 bridged funds forwarding further:

It is advised to check olas.balanceOf(distributorProxyAddress) and olas.balanceOf(unstakeRelayerProxyAddress) before executing function calls.

Redeem Operations

There could be scenarios when STAKE / UNSTAKE / UNSTAKE_RETIRED operations are not complete in an automatic way on L2 when triggered on L1. For example, OLAS funds arrive across bridge later than the message with the instruction about where funds need to be relayed. In this case, the RequestQueued() event in each DefaultStakingProcessorL2 is emitted with the following variables:

enum RequestStatus {
    NON_EXISTENT,
    EXTERNAL_CALL_FAILED,
    INSUFFICIENT_OLAS_BALANCE,
    UNSUPPORTED_OPERATION_TYPE,
    CONTRACT_PAUSED
}

event RequestQueued(bytes32 indexed batchHash, address indexed target, uint256 amount, bytes32 operation, RequestStatus status);

In order to complete the queued request, the agent must call the redeem() function using values from the RequestQueued() event:

/// @dev Redeems queued staking deposit / withdraw.
/// @param batchHash Batch hash.
/// @param target Staking target address.
/// @param amount Staking amount.
/// @param operation Funds operation: stake / unstake.
function redeem(bytes32 batchHash, address target, uint256 amount, bytes32 operation) external;

It is recommended to get the hash of the redeem operation with the following function:

/// @dev Gets failed request queued hash.
/// @param batchHash Batch hash.
/// @param target Staking target address.
/// @param amount Staking amount.
/// @param operation Funds operation: stake / unstake.
function getQueuedHash(bytes32 batchHash, address target, uint256 amount, bytes32 operation) public view returns (bytes32);

and check that the redeem is still queued with the following function using the queued hash value:

function queuedHashes(bytes32) external view returns (RequestStatus);

Note that STAKE operations will be redeemed either to finalize the stake, if queued due to insufficient OLAS balance (INSUFFICIENT_OLAS_BALANCE), or to be transferred back to L1 in all other cases.

Queued UNSTAKE and UNSTAKE_RETIRED operations must be always redeemed, as changes were already recorded on L1. If for some reason redeem keeps on failing, agents need to continue trying to redeem once in a while, as the failed request will be identified and the fix will be in place.

Note that UNSUPPORTED_OPERATION_TYPE queued status will never be delivered within the current contract scope, so agents are free to ignore those requests. However, proper separation of events is required.

Claim Reward Tokens

Each staked service has a controlling ActivityModule contract, which serves as an entry point to all the LST service activity. In order to claim reward tokens and immediately transfer them to Collector contract, agents need to call the claim() function, which triggers the checkpoint() function call as well of a corresponding stakingProxy contract.

Events to track staking proxy addresses, staked service Ids and their corresponding activity modules in StakingManager proxy contract:

event Staked(address indexed stakingProxy, uint256 indexed serviceId, address activityModule);
event Unstaked(address indexed stakingProxy, uint256 indexed serviceId, address activityModule);

Helper function to get all the staked services in StakingManager proxy contract:

/// @dev Gets staked service Ids for a specific staking proxy.
/// @param stakingProxy Staking proxy address.
/// @return serviceIds Set of service Ids.
function getStakedServiceIds(address stakingProxy) external view returns (uint256[] memory serviceIds);

Ultimately there is a set of active activity module addresses that must be called by agents to get rewards.

Staking Proxy Checkpoint

For any of stakingProxy address, the checkpoint() function can be called at any moment. However, note that the checkpoint() is also called when stake(), claim() and ustake() are executed. If applicable, it is advised to monitor the following condition prior to calling the checkpoint() function:

if ((stakingProxy.getNumServiceIds() > 0) && (block.timestamp - stakingProxy.tsCheckpoint() > stakingProxy.livenessPeriod())) {
    send stakingProxy.checkpoint();
}

This check is going to skip the checkpoint() call if no agents are staked or if the checkpoint has been already triggered within the livenessPeriod time.

External Staking (ExternalStakingDistributor)

The ExternalStakingDistributor manages staking on third-party staking proxy contracts. It deploys services (creating Safe multisigs with itself as module), stakes/unstakes/re-stakes them, and claims and distributes rewards.

Staking: Whitelisted curating agents (or the owner) call stake() to deploy a service on a whitelisted staking proxy. The contract creates a Safe multisig, registers it as a service, and stakes it. The curating agent is recorded per service.

Claiming rewards: Agents call claim() with arrays of staking proxies and service Ids. Rewards are distributed according to per-proxy configurable factors (must sum to 100%):

  • Collector share — sent to Collector via topUpBalance(amount, REWARD) for L1 bridging
  • Protocol share — sent to Collector via topUpProtocol(amount) for protocol use
  • Curating agent share — transferred directly to the curating agent address

For V1 staking types, rewards land on the service multisig and are distributed via execTransactionFromModule. For V2 staking types, rewards land directly on the contract.

Unstaking: Managing agents (or the owner) call unstakeAndWithdraw() to unstake a service, distribute remaining rewards, and optionally fulfill pending withdraw requests through Collector. Anyone can trigger unstake if the staking proxy has zero available rewards or if the service is evicted.

Re-staking: If a service is evicted, curating agents, managing agents, or the owner can call reStake() to unstake and immediately re-stake it.

Events to track external staking activity:

event ExternalServiceStaked(address indexed sender, address indexed stakingProxy, uint256 indexed serviceId, uint256 agentId, bytes32 configHash, uint256 stakingDeposit, uint256 stakedBalance);
event ExternalServiceUnstaked(address indexed sender, address indexed stakingProxy, uint256 indexed serviceId, uint256 stakingDeposit, uint256 stakedBalance);
event ExternalServiceRestaked(address indexed sender, address indexed stakingProxy, uint256 indexed serviceId);
event Claimed(address[] stakingProxies, uint256[] serviceIds, uint256[] rewards);

Trigger L2 to L1 Tokens Bridging

Each L2 Collector proxy contract collects OLAS from REWARD / UNSTAKE / UNSTAKE_RETIRED operations that are sent to L1.

The following event is emitted when the Collector is top upped:

event OperationReceiverBalancesUpdated(bytes32 indexed operation, address indexed receiver, uint256 balance);

At the same time, each corresponding operation balance can be fetched any moment using the following public getter:

function mapOperationReceiverBalances(bytes32 operation) external view returns (ReceiverBalance memory);

If balances are not smaller than the Collector.MIN_OLAS_BALANCE() value, the following function can be triggered by agents:

/// @dev Relays tokens to L1.
/// @param operation Operation type related to L1 receiver.
/// @param bridgePayload Bridge payload.
function relayTokens(bytes32 operation, bytes memory bridgePayload) external payable;

Here is the values of currently supported operations:

// Reward operation
REWARD = "0x0b9821ae606ebc7c79bf3390bdd3dc93e1b4a7cda27aad60646e7b88ff55b001";

// Unstake operation
UNSTAKE = "0x8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e732293";

// Unstake-retired operation
UNSTAKE_RETIRED = "0x9065ad15d9673159e4597c86084aff8052550cec93c5a6e44b3f1dba4c8731b3";