├── .gitmodules ├── README.md ├── foundry.toml ├── src ├── ArbitraryIntent.sol ├── Structs.sol └── TheCircusZone.sol └── test ├── ArbitraryIntent.t.sol ├── TheCircusZone.t.sol └── helpers ├── SpecificIntent.sol └── SpecificIntent.t.sol /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/seaport-sol"] 5 | path = lib/seaport-sol 6 | url = https://github.com/projectopensea/seaport-sol 7 | [submodule "lib/seaport-types"] 8 | path = lib/seaport-types 9 | url = https://github.com/projectopensea/seaport-types 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Circus 2 | 3 | ## Q: What do Seaport Orders and The Circus have in common? 4 | A: They're both intents. 5 | 6 | ---- 7 | 8 | ## Usage 9 | 10 | `TheCircusZone` is a proof-of-concept Seaport Zone that takes `extraData` in the form of an abi-encoded struct: 11 | 12 | ```solidity 13 | struct ArbitraryIntentData { 14 | bytes initData; 15 | bytes32 salt; 16 | } 17 | ``` 18 | 19 | This data is used to CREATE2 an ephemeral smart contract designed to be a container to execute arbitrary logic within its constructor, without actually writing code to the chain. 20 | 21 | This allows for expressive, arbitrary assertions to be made by `TheCircusZone`, to emphasize Seaport's potential as an "intent" protocol; a signed Seaport order will not be fulfillable unless all `ConsiderationItem`s are satisified _in addition_ to any arbitrary checks specified by an `ArbitraryIntent`. Since Zones can both read _and_ write arbitrary data, this allows for a wide range of use cases. 22 | 23 | ### TheCircusZone 24 | 25 | A simple zone that reads the `extraData` from the `ZoneParameters` as an `ArbitraryIntentData` struct. It calls `CREATE2` using this data, and reverts if any data returned, otherwise passing the `validateOrder` check. The resulting address is checked against the `zoneHash` of the order, meaning all logic must be set at the time of order creation. 26 | 27 | ### ArbitraryIntent 28 | 29 | An abstract template contract for executing arbitrary code within the context of a Seaport Zone – "intent" logic is implemented in the `expressIntent()` function. If a revert is specified with a non-zero amount of data, `TheCircusZone` will revert with this data, otherwise, the `validateOrder` check will pass. 30 | 31 | ### `test/helpers/SpecificIntent.sol` 32 | 33 | A very simple "specific" Intent that reverts if a particular smart contract has not been deployed. 34 | 35 | # FAQ 36 | 37 | ## What are intents? 38 | idk tbh 39 | 40 | ## What is Seaport? 41 | [Seaport](https://github.com/ProjectOpenSea/seaport) is the most powerful, extensible, and gas-efficient token settlement platform on EVM chains. 42 | 43 | ## What are Seaport Zones? 44 | Zones are accounts that must authorize a Seaport order as part of fulfillment. Strictly speaking, they don't _have_ to be smart contracts, but they're a lot more interesting if they are. 45 | 46 | Seaport will call `validateOrder` on smart contract Zones specified by orders' `OrderParameters`, after all token transfers have been made. This means Zones can make arbitrary assertions about the state of the chain _after_ the order has been executed, such as: 47 | 48 | > This account should have at least X balance of Y token, and at most B balance of C token 49 | 50 | without being prescriptive about how exactly that happens. 51 | 52 | Since Seaport makes a stateful call, Zones may also perform on-chain update. 53 | 54 | ## Why CREATE2 "intents?" 55 | To highlight that Seaport Zones can do pretty much anything 56 | 57 | ## Isn't that pretty gas-intensive? 58 | Yeah 59 | 60 | ## Can't I just deploy a custom Seaport Zone for my use-case? 61 | Yeah 62 | 63 | ## How is "The Circus" different from normal Seaport Zones? 64 | It's not -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc = '0.8.20' 3 | src = "src" 4 | out = "out" 5 | libs = ["lib"] 6 | remappings = [ 7 | 'seaport-sol/=lib/seaport-sol/src/', 8 | 'seaport-types/=lib/seaport-types/src/', 9 | ] 10 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 11 | -------------------------------------------------------------------------------- /src/ArbitraryIntent.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | /** 5 | * @title ArbitraryIntent 6 | * @author emo.eth 7 | * @notice This template is meant to be used in concert with the Circus zone to make arbitrary assertions about smart 8 | * contract and chain state. 9 | */ 10 | abstract contract ArbitraryIntent { 11 | constructor() payable { 12 | expressIntent(); 13 | // finalize by returning nothing – saving gas 14 | assembly { 15 | return(0, 0) 16 | } 17 | } 18 | 19 | /** 20 | * @dev Make arbitrary assertions about smart contract and chain state 21 | */ 22 | function expressIntent() internal virtual; 23 | } 24 | -------------------------------------------------------------------------------- /src/Structs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | struct ArbitraryIntentData { 5 | /** 6 | * @dev the initData for a "smart contract" intended to make all assertions in its constructor, else revert 7 | */ 8 | bytes initData; 9 | /** 10 | * @dev the create2 salt for the "smart contract" so that the address can be used as the zoneHash, and identical 11 | * initCode can be used in the same transaction (by specifying different salts) 12 | */ 13 | bytes32 salt; 14 | } 15 | -------------------------------------------------------------------------------- /src/TheCircusZone.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ZoneInterface} from "seaport-types/interfaces/ZoneInterface.sol"; 5 | import {ZoneParameters, Schema} from "seaport-types/lib/ConsiderationStructs.sol"; 6 | import {ArbitraryIntentData} from "./Structs.sol"; 7 | 8 | contract TheCircusZone is ZoneInterface { 9 | error InvalidExtraData(); 10 | 11 | /** 12 | * @dev Validates an order. 13 | * 14 | * @param zoneParameters The context about the order fulfillment and any 15 | * supplied extraData. 16 | * 17 | * @return validOrderMagicValue The magic value that indicates a valid 18 | * order. 19 | */ 20 | function validateOrder(ZoneParameters calldata zoneParameters) external returns (bytes4 validOrderMagicValue) { 21 | bytes calldata extraData = zoneParameters.extraData; 22 | ArbitraryIntentData calldata arbitraryIntentData; 23 | // manually create a calldata pointer to arbitraryIntentData directly 24 | // validation is unnecessary because comparing the resulting CREATE2 address to the zoneHash implicitly does 25 | // that 26 | assembly { 27 | arbitraryIntentData := add(extraData.offset, 0x20) 28 | } 29 | 30 | // copy initcode to memory 31 | bytes memory arbitraryIntentInit = arbitraryIntentData.initData; 32 | // get salt for CREATE2 33 | bytes32 arbitraryIntentSalt = arbitraryIntentData.salt; 34 | 35 | // load zoneHash from calldata and declare variable to store the result of comparing CREATE2 address to 36 | // zoneHash 37 | bytes32 zoneHash = zoneParameters.zoneHash; 38 | bool validZoneHash; 39 | 40 | assembly { 41 | // store resulting create2 address 42 | let result := create2(0, add(arbitraryIntentInit, 0x20), mload(arbitraryIntentInit), arbitraryIntentSalt) 43 | // check if resulting address matches zoneHash 44 | validZoneHash := eq(result, zoneHash) 45 | // check if any data was returned – assume that if data was returned, it was a revert message 46 | if returndatasize() { 47 | // if data was returned, revert with that data 48 | returndatacopy(0, 0, returndatasize()) 49 | revert(0, returndatasize()) 50 | } 51 | } 52 | 53 | if (!validZoneHash) { 54 | revert InvalidExtraData(); 55 | } 56 | 57 | return ZoneInterface.validateOrder.selector; 58 | } 59 | 60 | /** 61 | * @dev Returns the metadata for this zone. 62 | * 63 | * @return name The name of the zone. 64 | * @return schemas The schemas that the zone implements. 65 | */ 66 | function getSeaportMetadata() external pure returns (string memory name, Schema[] memory schemas) { 67 | return ("Circus", schemas); 68 | } 69 | 70 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 71 | return interfaceId == type(ZoneInterface).interfaceId; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/ArbitraryIntent.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {SpecificIntent, CHECK_DEPLOYED} from "./helpers/SpecificIntent.sol"; 6 | 7 | contract ArbitraryIntentTest is Test { 8 | function testLeaveNoTrace() public { 9 | vm.etch(CHECK_DEPLOYED, "deployed"); 10 | SpecificIntent intent = new SpecificIntent(); 11 | assertEq(address(intent).code.length, 0, "should not have deployed code"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/TheCircusZone.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {SpecificIntent, CHECK_DEPLOYED} from "./helpers/SpecificIntent.sol"; 6 | import {TheCircusZone, ZoneInterface} from "../src/TheCircusZone.sol"; 7 | import {ZoneParameters} from "seaport-types/lib/ConsiderationStructs.sol"; 8 | import {ArbitraryIntentData} from "../src/Structs.sol"; 9 | 10 | contract TheCircusZoneTest is Test { 11 | TheCircusZone test; 12 | address constant CREATE2_ADDRESS = 0x67385EF0f630d767Db1197AEd245F2E566A372Cb; 13 | 14 | function setUp() public { 15 | test = new TheCircusZone(); 16 | } 17 | 18 | function testValidateOrderSuccess() public { 19 | ZoneParameters memory params; 20 | params.zoneHash = bytes32(uint256(uint160(CREATE2_ADDRESS))); 21 | ArbitraryIntentData memory data = 22 | ArbitraryIntentData({initData: type(SpecificIntent).creationCode, salt: bytes32(uint256(1))}); 23 | params.extraData = abi.encode(data); 24 | vm.etch(CHECK_DEPLOYED, "deployed"); 25 | bytes4 magicValue = test.validateOrder(params); 26 | assertEq(magicValue, ZoneInterface.validateOrder.selector); 27 | } 28 | 29 | function testValidateOrderConstructorRevert() public { 30 | ZoneParameters memory params; 31 | params.zoneHash = bytes32(uint256(uint160(CREATE2_ADDRESS))); 32 | ArbitraryIntentData memory data = 33 | ArbitraryIntentData({initData: type(SpecificIntent).creationCode, salt: bytes32(uint256(1))}); 34 | params.extraData = abi.encode(data); 35 | vm.expectRevert(SpecificIntent.NotDeployed.selector); 36 | test.validateOrder(params); 37 | } 38 | 39 | function testValidateOrderInvalidZoneHash() public { 40 | ZoneParameters memory params; 41 | params.zoneHash = bytes32(uint256(uint160(CREATE2_ADDRESS))); 42 | ArbitraryIntentData memory data = ArbitraryIntentData({initData: type(SpecificIntent).creationCode, salt: 0}); 43 | params.extraData = abi.encode(data); 44 | vm.etch(CHECK_DEPLOYED, "deployed"); 45 | vm.expectRevert(TheCircusZone.InvalidExtraData.selector); 46 | test.validateOrder(params); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/helpers/SpecificIntent.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ArbitraryIntent} from "../../src/ArbitraryIntent.sol"; 5 | 6 | address constant CHECK_DEPLOYED = address(uint160(uint256(keccak256("CHECK_DEPLOYED")))); 7 | 8 | contract SpecificIntent is ArbitraryIntent { 9 | error NotDeployed(); 10 | 11 | function expressIntent() internal view override { 12 | if (CHECK_DEPLOYED.code.length == 0) { 13 | revert NotDeployed(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers/SpecificIntent.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {SpecificIntent, CHECK_DEPLOYED} from "./SpecificIntent.sol"; 6 | 7 | contract SpecificIntentTest is Test { 8 | function testDeploySuccess() public { 9 | vm.etch(CHECK_DEPLOYED, "deployed"); 10 | SpecificIntent intent = new SpecificIntent(); 11 | assertEq(address(intent).code.length, 0, "should not have deployed code"); 12 | } 13 | 14 | function testDeployedCheckFailure() public { 15 | vm.expectRevert(SpecificIntent.NotDeployed.selector); 16 | new SpecificIntent(); 17 | } 18 | } 19 | --------------------------------------------------------------------------------