Build the infrastructure for Orders
Overview
To implement automated Orders or Orders with price prediction, you should first build the infrastructure shared by both Order types.
This guide explains how to add such components as data structures, utils, deployment scripts, and so on. You'll also implement a transaction signing service using the x/warden
module.
Prerequisites
Before you start, meet the prerequisites.
1. Create data structures
First, you need to create contracts defining data structures and interfaces for Orders. Store these files in the src/types
directory.
-
Create a library
Types
with the core data structures.Include data structures for signature requests, different Order types, and oracle/prediction price pairs. In the enum
PriceCondition
, implement the following price conditions:enum PriceCondition {
LTE, // Less than or equal to the threshold
GTE, // Greater than or equal to the threshold
LT, // Less than the threshold
GT // Greater than the threshold
} -
Create a library
TypesV0
with the common execution data. -
Add the
IExecutionV0
file with the execution data structure and an interface for executing Orders.Include an
execute()
function for executing Orders,isExecuted()
for checking the execution status, and others.
2. Create helpers and utils
In the src/lib
directory, create helper and utility libraries for Orders:
-
To support EIP-1559 transactions, create a
Strings.sol
library implementing string operations. -
Create an
RLPEncode.sol
library implementing RLP encoding for EIP-1559 transactions. -
Create a helper contract
Create2.sol
.The main deployment script will use this contract for deploying the Order infrastructure with the
CREATE2
opcode.
3. Implement the registry
In the src
directory, add a file Registry.sol
implementing a registry for tracking transactions.
Include the register()
and addTransaction()
functions for registering Orders and storing transaction data.
4. Create mock precompiles
Mock precompiles are essential for end-to-end testing of the onchain Agent. In the mocks
directory, build three contracts mocking Warden precompiles:
-
Create a Slinky precompile mocking
x/oracle
. Its goal is providing oracle price feeds. -
Create a Warden precompile mocking
x/warden
. Its goal is managing keys and signature requests. -
Create an Async precompile mocking
x/async
. Its goal is executing Tasks using AVR Plugins. Note that this precompile is required only for automated Orders with price prediction.
You can test mock precompiles in the test
directory:
-
Create a helper contract for testing the precompiles:
contract PrecompileTestHelper {
MockSlinkyPrecompile internal slinky;
MockWardenPrecompile internal warden;
function setUp() public {
// Deploy and configure mocks
slinky = new MockSlinkyPrecompile();
warden = new MockWardenPrecompile();
// Inject mock addresses
vm.etch(ISLINKY_PRECOMPILE_ADDRESS, address(slinky).code);
vm.etch(IWARDEN_PRECOMPILE_ADDRESS, address(warden).code);
}
// A price scenario helper
function setupPriceScenario(
string memory base,
string memory quote,
uint256 initialPrice,
uint256 targetPrice
) internal {
MockSlinkyPrecompile(ISLINKY_PRECOMPILE_ADDRESS)
.setPrice(base, quote, initialPrice);
// Simulate a price change
skip(1 hours);
MockSlinkyPrecompile(ISLINKY_PRECOMPILE_ADDRESS)
.setPrice(base, quote, targetPrice);
}
} -
Create a scenario for testing the price feed:
contract SlinkyTest is PrecompileTestHelper {
function testPriceMovement() public {
// Set up a price scenario
setupPriceScenario("ETH", "USD", 3000e9, 3500e9);
// Test the Order execution
Types.OrderData memory orderData = createTestOrder(
3200e9, // The threshold
Types.PriceCondition.GTE
);
BasicOrder order = new BasicOrder(
orderData,
new CommonTypes.Coin[](0),
address(this),
address(registry)
);
assertTrue(order.canExecute());
}
function testPriceFeedErrors() public {
vm.expectRevert("Price not set");
slinky.getPrice("UNKNOWN", "PAIR");
}
} -
Test transaction signing:
contract WardenTest is PrecompileTestHelper {
function testSigningFlow() public {
// Set up keys
warden.addKey(1, true); // A valid key
warden.addKey(2, false); // An invalid key
// Test successful signing
Types.SignRequestData memory goodRequest = createSignRequest(1);
assertTrue(executeOrder(goodRequest));
// Test failed signing
Types.SignRequestData memory badRequest = createSignRequest(2);
assertFalse(executeOrder(badRequest));
}
function testInvalidInputs() public {
vm.expectRevert("Empty approve expression");
Types.SignRequestData memory invalidRequest = createSignRequest(1);
invalidRequest.expectedApproveExpression = "";
executeOrder(invalidRequest);
}
} -
Use precompiles in scripts:
contract CreateOrder is Script {
function run(
uint256 thresholdPrice,
Types.PriceCondition priceCondition,
Types.PricePair memory pricePair
) public {
// Set up mock precompiles
MockSlinkyPrecompile mSlinky = new MockSlinkyPrecompile();
MockWardenPrecompile mWarden = new MockWardenPrecompile();
// Configure the initial state
vm.etch(ISLINKY_PRECOMPILE_ADDRESS, address(mSlinky).code);
mSlinky.setPrice(pricePair.base, pricePair.quote, thresholdPrice);
vm.etch(IWARDEN_PRECOMPILE_ADDRESS, address(mWarden).code);
mWarden.addKey(1, true);
// Create and verify an Order
vm.broadcast();
BasicOrder order = createOrder(/* params */);
require(order.canExecute(), "Order cannot execute");
}
}
5. Implement transaction signing
In the src/orders
directory, create an abstract contract AbstractOrderV0.sol
for signing transactions. It'll be called by all types of Orders.
Your code should include a function createSignRequest()
that creates signature requests for Keychains. Use the newSignRequest()
function of the Warden precompile.
6. Implement Order creation
In the src/factories
directory, create an OrderFactory
contract for managing the creation and tracking of Orders.
Include the createOrder()
and computeOrderAddress()
functions for triggering Order deployment and computing the deterministic address of an Order.
Depending on the Order type selected by a user, OrderFactory
should invoke either BasicOrderFactory
or AdvancedOrderFactory
and then emit an OrderCreated
event. See the _createBasicOrder()
and _createAdvancedOrder()
functions.
7. Create deployment scripts
Finally, implement deployment scripts in the script
directory:
-
Implement the main script for deploying the Order infrastructure.
It should handle the following tasks:
- Deploy
OrderFactory
- Deploy
Registry
- Configure the environment
- Deploy
-
Implement a script for creating Orders.
It should handle the following tasks:
- Trigger
OrderFactory
to invokeBasicOrderFactory
/AdvancedOrderFactory
- Set up mock precompiles
- Configure parameters
- Trigger
Next steps
Now you can implement automated Orders.