-
Notifications
You must be signed in to change notification settings - Fork 14
StaticCallAssetProxy #39
Description
Summary
We introduce a new asset proxy for validating an order bundle during settlement through an arbitrary, read-only callback.
Motivation
One issue when trading non-fungible items (kitties, composables, CDPs) is that many of these assets are stateful. Their value is derived from the state at a given point in time. If this state were to be modified, the value of the asset is also potentially modified.
We wish to implement a method to guarantee for the buyer of an asset that the state remains desirable throughout the entire settlement process.
StaticCallProxyAssetData Fields
struct StaticCallProxyAssetData {
// Asset proxy ID.
bytes4 assetProxyId, // bytes4(keccak256("StaticCallAssetProxy(...)")),
// Address of the call target.
address callTarget,
// ABI-encoded call data to pass to the call target,
// generated by `abi.encodeWithSelector()`.
bytes callData,
// Keccak256 hash of the expected, ABI-encoded result of the call.
// Will revert if the hash of the actual result does not match.
bytes32 callResultHash,
}`Usage
The StaticCallAssetProxy “asset” is intended to be mixed with real assets within a MultiAssetProxy bundle, such as:
// An asset bundle that transfers a crypto kitty and verifies that it is
// ready to breed.
MultiAssetProxyAssetData(
// Amounts of each "asset". Because StaticCallAssetProxy assets
// cannot modify state, we should only call them once.
[1, 1],
// The "assets" in this bundle.
[
// The crypto ktty ERC721 asset to transfer.
ERC721AssetProxyAssetData(CRYPTO_KITTIES_CONTRACT, kittyId),
// Validation callback to verify that `kittyId` is ready to breed.
StaticCallAssetProxy(
// `callTarget`
CRYPTO_KITTIES_CONTRACT,
// `callData`
abi.encodeWithSelector(
// Call the `isReadyToBreed()` function.
bytes4(keccak256("isReadyToBreed(bytes32)")),
// Pass `kittyId`.
kittyId
),
// `callResult`: `isReadyToBreed(kittyId)` should return `true`.
keccak256(abi.encode(true))
)
]
)When the MultiAssetProxy executes the transfer of this asset, the StaticCallAssetProxy will perform the following read-only check:
keccak256(assetData.callTarget.staticcall(assetData.callData)) == assetData.callResultHash If the above condition is not satisfied, the asset proxy will revert, aborting the entire trade. A maker can even chain multiple StaticCallAssetProxy assets inside the MultiAssetProxy bundle to perform multiple state checks, or they can use a bespoke contract to aggregate all checks (or perform more complex checks) in a single call.
Altogether, this allows the maker to establish some sort of state guarantees on the asset(s) being traded. And, since callbacks can access any on-chain state, we imagine some pretty creative uses could arise.
StaticCallAssetProxy Implementation
A pseudo-code implementation of the asset proxy would look like:
contract StaticCallAssetProxy is
MixinAuthorizable
{
/// @dev The main entry point for the asset proxy.
function transferFrom(
bytes StaticCallAssetProxyData assetData,
// These parameters are ignored/have no relevance.
address from,
address to,
uint256 amount
)
external
view
onlyAuthorized
{
(bool success, bytes returnData) = assetData.callTarget.staticcall(
assetData.callData
);
// Abort if the call reverted or tried to modify state.
require(success, returnData);
// Abort if the hash of the return data does not match what is expected.
require(keccak256(returnData) == callData.callResultHash);
}
}Example Use Cases
- Protect a CDP from being liquidated and incurring a 13% liquidation fee by creating a 0x order to sell your CDP at a slight discount IF AND ONLY IF (IFF) the collateral-to-DAI ratio for your CDP is below 155%. Liquidation occurs when collateral-to-DAI falls below 150%. Note that this would rely on MakerDAO's on-chain ETH/DAI price feed or some other oracle solution. Similar to CDP Saver but native to 0x.
- Purchase a Decentraland Estate IFF it contains specific parcels of LAND.
- Offer to purchase a parcel of Decentraland LAND IFF neighboring parcel located at [x,y] is NOT owned by a specific address (“bad neighbor” clause).
Challenges
- It is not very intuitive to treat this feature as an “asset,” as it can have no value. Without proper safeguarding, it's possible to trade a real asset for a worthless StaticCall “asset.”
- Complex, non-aggregate state checks require deploying a contract.
- The validity of an order can be much more difficult to anticipate in a general sense, as any state outside the assets being traded can render an order invalid. Issuing an
eth_callfill is the only way to generally validate these asset bundles. - The maker dictates the state guarantees on the asset, which might not exactly align with the taker's interests. Some kind of Exchange wrapper/extension might be helpful in these cases.
- The
from,to, andamountfields are currently ignored, but could be useful to the callback— for example, to check the KYC status of the taker.