Implement the creation of Orders
Overview
This article will guide you through creating the AdvancedOrderFactory
contract. AdvancedOrderFactory
, when triggered by OrderFactory
, deploys Orders (instances of AdvancedOrder
) and registers them in the registry.
This factory pattern supports deterministic address computation, front-running protection, and salt-based deployment security. Note that it extends the basic automated Orders creation.
You'll implement the following core components:
contract AdvancedOrderFactory is ReentrancyGuard {
Registry public immutable REGISTRY;
mapping(bytes32 salt => bool used) public usedSalts;
event AdvancedOrderCreated(
address indexed creator,
address orderAddress
);
event SaltUsed(
bytes32 indexed salt,
address indexed creator
);
}
Store AdvancedOrderFactory
in the /src
directory, alongside with other contracts.
You can find the full code on GitHub: /src/AdvancedOrderFactory.sol
1. Implement the Order creation logic
Implement the core function for deploying new Orders:
function createAdvancedOrder(
Types.AdvancedOrderData calldata orderData,
Types.CommonExecutionData calldata executionData,
CommonTypes.Coin[] calldata maxKeychainFees,
address scheduler,
bytes32 salt
) external nonReentrant returns (address orderAddress) {
// Front-running protection using tx.origin
address origin = tx.origin;
bytes32 guardedSalt = keccak256(
abi.encodePacked(uint256(uint160(origin)), salt)
);
if (usedSalts[guardedSalt]) {
revert SaltAlreadyUsed();
}
emit SaltUsed(guardedSalt, origin);
// Encode contract creation with prediction setup
bytes memory bytecode = abi.encodePacked(
type(AdvancedOrder).creationCode,
abi.encode(
orderData,
executionData,
maxKeychainFees,
scheduler,
address(REGISTRY)
)
);
// Deploy with the CREATE3 opcode
orderAddress = Create3.create3(guardedSalt, bytecode);
address expectedAddress = Create3.addressOf(guardedSalt);
if (orderAddress == address(0) ||
orderAddress != expectedAddress) {
revert OrderDeploymentFailed(guardedSalt);
}
// Register and track the Order
REGISTRY.register(orderAddress);
usedSalts[guardedSalt] = true;
emit AdvancedOrderCreated(msg.sender, orderAddress);
}
2. Implement data validation
Create functions for validating the Order data:
function _validateOraclePair(
Types.PricePair calldata pair
) internal pure returns (bool) {
return bytes(pair.base).length > 0 &&
bytes(pair.quote).length > 0;
}
function _validatePredictionPair(
Types.PricePair calldata pair
) internal pure returns (bool) {
return bytes(pair.base).length > 0 &&
bytes(pair.quote).length > 0;
}
function _validateAdvancedOrderData(
Types.AdvancedOrderData calldata data
) internal pure {
if (!_validateOraclePair(data.oraclePricePair)) {
revert InvalidOraclePair();
}
if (!_validatePredictionPair(data.predictPricePair)) {
revert InvalidPredictionPair();
}
if (data.priceCondition > Types.PriceCondition.GT) {
revert InvalidPriceCondition();
}
}
3. Add address computation
function computeOrderAddress(
address origin,
bytes32 salt
) external view returns (address) {
bytes32 guardedSalt = keccak256(
abi.encodePacked(uint256(uint160(origin)), salt)
);
return Create3.addressOf(guardedSalt);
}
4. Implement tests
Finally, implement tests:
contract AdvancedOrderFactoryTest is Test {
function test_CreateAdvancedOrder() public {
Types.AdvancedOrderData memory orderData = Types.AdvancedOrderData({
oraclePricePair: Types.PricePair({
base: "ETH",
quote: "USD"
}),
predictPricePair: Types.PricePair({
base: "ethereum",
quote: "tether"
}),
priceCondition: Types.PriceCondition.GT
});
bytes32 salt = bytes32("test");
address expected = factory.computeOrderAddress(
address(this),
salt
);
vm.expectEmit(true, true, false, false);
emit AdvancedOrderCreated(address(this), expected);
address actual = factory.createAdvancedOrder(
orderData,
executionData,
maxKeychainFees,
scheduler,
salt
);
assertEq(actual, expected);
assertTrue(registry.isRegistered(actual));
// Verify prediction setup
AdvancedOrder order = AdvancedOrder(actual);
assertTrue(order.futureId() > 0);
}
function test_InvalidPricePairs() public {
Types.AdvancedOrderData memory orderData = Types.AdvancedOrderData({
oraclePricePair: Types.PricePair({
base: "",
quote: "USD"
}),
predictPricePair: Types.PricePair({
base: "ethereum",
quote: "tether"
}),
priceCondition: Types.PriceCondition.GT
});
vm.expectRevert(InvalidOraclePair.selector);
factory.createAdvancedOrder(
orderData,
executionData,
maxKeychainFees,
scheduler,
bytes32("test")
);
}
function test_SaltReuse() public {
bytes32 salt = bytes32("test");
factory.createAdvancedOrder(...);
vm.expectRevert(SaltAlreadyUsed.selector);
factory.createAdvancedOrder(...);
}
}
Security measures
In the previous steps, you've implemented the following security measures:
- Price data validation
The contract will check if oracle and predictions price pairs are properly formatted and asset symbols match the expected formats.function _validateOraclePair(...)
function _validatePredictionPair(...)
function _validateAdvancedOrderData(...) - Salt management
Salts are guarded bytx.origin
to prevent front-running. Each salt can only be used once per creator.address origin = tx.origin;
bytes32 guardedSalt = keccak256(
abi.encodePacked(uint256(uint160(origin)), salt)if (usedSalts[guardedSalt]) {
revert SaltAlreadyUsed();
}
Next steps
After creating the AdvanceOrderFactory
contract, you can deploy an Order.