Skip to main content

Bootstrapping From an Existing Module

Please visit the modules section to discover what modules are already available to bootstrap from.

In this guide, we'll analyze the V2RewardDAppControl contract from the official Atlas repository.

1. Module Summary

This is a DEX backrun module for Uniswap v2 forks. Users usually make their swaps on the dApp frontend, and sign their transactions (swaps) with the dApp router as target. By integrating this module, the user swaps will now be wrapped into Atlas user operations, and sent to the Atlas auctioneer, for auctioning.

Users no longer approve the dApp's router to transfer tokens from them, but Atlas.

Solvers interested in backrunning a user's swap (i.e., for arbitrage) will bid in the auction, and eventually, an Atlas bundler will group all those operations together and send a single transaction to the blockchain.

Here, we're interested in the dApp control contract, where these rules and options are defined.

2. Module Structure

a. Inheritance

The module is inheriting from DAppControl (Atlas official repository). This is the recommended pattern, so we can focus on the customization of our module. This is also true when writing a module from scratch.

b. Call Config

The call config defines the options of our module, and must be passed to the parent DAppControl during initialization. These options must be carefully reviewed as they will influence the behavior of the Atlas transactions for this module. Here is the full call config of V2RewardDAppControl and its meaning.

call config
{
// User operations nonces are not required to be included sequentially
userNoncesSequential: false,

// DApp operations nonces are not required to be included sequentially
dappNoncesSequential: false,

// The `preOps` hooks (defined in this module) will be run during Atlas execution
requirePreOps: true,

// The data returned by the `preOps` hook will be passed down to other hooks
trackPreOpsReturnData: true,

// The data returned by the user operation execution will not be passed down to other hooks
trackUserReturnData: false,

// The user operation will not be executed by delegatecalling (standard call)
delegateUser: false,

// The `preSolver` hook will not be executed
requirePreSolver: false,

// The `postSolver` hook will not be executed
requirePostSolver: false,

// The Atlas transaction remains valid even when no solvers are included (we still want the user's swap to go through)
zeroSolvers: true,

// Forces an Atlas transaction to not revert when preliminary validation fails, to ensure the user operation nonce is consumed
reuseUserOp: false,

// The user is allowed to be the auctioneer (they can generate the dApp operations)
userAuctioneer: true,

// Solvers are not allowed to be the auctioneer
solverAuctioneer: false,

// Auctioneer role can be taken by any party
unknownAuctioneer: true,

// The call chain hash generated in the dApp operation will be validated by the Atlas contract
verifyCallChainHash: true,

// The data returned by the previous hooks will be passed down the solver call
forwardReturnData: false,

// Do not revert an Atlas transaction when all solvers fail (we still want the user's swap to go through)
requireFulfillment: false,

// User operation hash will be computed using the trusted approach
trustedOpHash: true,

// Do not invert the solver bid values (highest bid should win)
invertBidValue: false,

// Solver bids will be computed at execution time, and not assumed from their solver operation `bid` field value
exPostBids: true

// Solvers sequence execution will stop whn a successful one is found
multipleSuccessfulSolvers: false
}

c. Auction rules

This module enforces solvers to bid in the REWARD_TOKEN currency. This token is set in the constructor during initialization, and the rule is enforced by overriding the getBidFormat function.

d. Hooks

This module is defining custom code for the following hooks.

_checkUserOperation

In this hook, we check that the user operation's dapp field is equal to the dApp's router contract, as defined in the constructor of the module. Remember the user's swap which used to be an actual transaction is now wrapped into an Atlas user operation. The dapp field of this operation must be the router contract (as it was the to field of the former transaction). This check ensures the user operation will be routed to the correct destination.

_preOpsCall

This hook is run before the actual user operation's execution. It takes the user operation's data field and extract the token being swapped (or sold) from it. It takes the right amount of those tokens from the user (remember the user must have approved Atlas to transfer from them instead of the dApp's router), and in turn, approve the dApp's router to transfer from it. It then returns the address of the token being swapped out (the trackPreOpsReturnData call config option is enabled), that will be passed down to the _allocateValueCall hook.

warning

This hook is delegatecalled, all actions are taken on behalf of the execution environment.

_allocateValueCall

This hook is called after the user operation and a solver operation successfully execute. It dispatches the full paid bid amount back to the user.

3. Initialization

Once deployed, the module needs to be enabled on Atlas. This is done by calling the initializeGovernance function on the AtlasVerification contract. The function must be called by the module's deployer (referred as governance in Atlas).

Initializing a module
interface IAtlasVerification {
function initializeGovernance(address control) external;
}

address atlasVerificationAddress = address(0x01);
address moduleAddress = address(0x02);

// Activating our module (dApp control)
IAtlasVerification(atlasVerificationAddress).initializeGovernance(moduleAddress);

4. Conclusion

If we are a Uniswap v2 fork DEX looking at integrating Atlas, we can do so easily by bootstrapping from the V2RewardDAppControl. The module can be deployed as is, or we can alter its behavior as needed, by tweaking the call config options and further customizing the hooks.

Once deployed and initialized, we would need to make the appropriate changes to our frontend. Find a complete example in the frontend changes for dApps integration guide.