ESIP-2: Safe Trustless Smart Contract Ethscription Escrow
Discuss and offer feedback in this GitHub Issue.
Abstract
This proposal introduces ESIP-2, an enhancement to the Ethscriptions Protocol that enables smart contracts to safely and trustlessly escrow Ethscriptions.
ESIP-2 accomplishes this by offering a mechanism for conditional transfers, relieving contracts from the requirement to identify the depositor of a given Ethscription.
Specification
Add a new smart contract event into the Ethscriptions Protocol:
When a contract emits this event, the protocol should register a valid ethscription transfer from the emitting contract to recipient
of the ethscription with id ethscriptionId
, provided:
The emitting contract owns the ethscription with id
ethscriptionId
when it emits the event.The ethscription's previous owner was
previousOwner
as defined below.
An ethscription's "current owner" is the address that is in the "to" of the most recent valid transfer of that ethscription.
An ethscription's "previous owner" is the address that is in the "from" of the most recent valid transfer.
"Previous owner" doesn't necessarily mean "previous unique owner." For example, if you transfer an ethscription to me and then I transfer it to myself, I will be both the "current owner" and the "previous owner."
Implementation Guidelines
After ESIP-2, a valid ethscription transfer must have two properties:
Its "from" must equal the "to" of the previous valid transfer
Transfers sent under ESIP-2 will have an "enforced previous owner." In the case one exists, the enforced previous owner must equal the "from" of the previous valid transfer.
Below is an example of how an Ethscription transfers could be validated after the implementation of ESIP-2:
Rationale
ESIP-2 is formulated primarily to enable smart contracts to safely escrow ethscriptions.
The idea of the smart contract escrow is that you send an ethscription to a smart contract, and, though that ethscription is owned by the smart contract, you retain some power over it—typically the ability to withdraw it and the ability to instruct the smart contract to send it to someone else.
Marketplaces are a common use-case for smart contract ethscription escrow. Because it is currently not possible for people to give smart contracts approval to transfer their ethscriptions, in order to list an ethscription for sale it must be transferred to the marketplace contract first.
With the introduction of ethscriptions_protocol_TransferEthscription
in ESIP-1, smart contracts have the capability send and receive ethscriptions and function as marketplaces / escrows. However with just ESIP-1, smart contracts cannot obtain the information required to function as safe escrows without additional help.
The purpose of ESIP-2 is to enable smart contracts to overcome this limitation.
Who is the Depositor?
As an escrow, a smart contract should act to the benefit of the depositor of a given ethscription. However, because smart contracts cannot access ethscription ownership information, contracts cannot determine who deposited a given ethscription.
For example, if Alice and Bob both send a transaction to a smart contract with calldata 0xb1bdb91f010c154dd04e5c11a6298e91472c27a347b770684981873a6408c11c
, the smart contract can recognize this as a potential deposit, but it cannot know which (if either) of Alice or Bob's transactions is a legitimate deposit.
Because the contract can't determine the ethscription's depositor, it cannot determine who should have the power to control the ethscription once deposited. For example, the smart contract cannot determine who should have the power to withdraw the ethscription.
Because a smart contract cannot distinguish between Alice and Bob's deposits, it might treat them equally, leading to this exploit:
Alice "Deposits" id 0x123
Bob Deposits id 0x123
(Bob's deposit is real, Alice's isn't)
Alice requests a withdraw
Contract emits
ethscriptions_protocol_TransferEthscription(Alice, 0x123)
Bob requests a withdraw
Contract emits
ethscriptions_protocol_TransferEthscription(Bob, 0x123)
Alice owns the ethscription after (5) and the transfer in (7) fails.
Giving Contracts More Information
The most straightforward way to avoid this exploit is to require a trusted third party to confirm which deposits are valid.
For example, after you deposited id 0x123 you could go to the trusted party, ask them to verify it was your deposit that caused the contract to own id 0x123, and create a signed message memorializing this information.
Then you could present this signed message to the escrow contract to prove your deposit was legitimate. The contract would know to believe your message by comparing the signer of the message to the address of the trusted party.
Finally, when deposits are pending confirmation, they cannot be withdrawn, because the contract doesn’t know who should have the ability to do so.
Here's how the exploit would be foiled using this approach:
Alice "Deposits" id 0x123
Bob Deposits id 0x123
Smart Contract Freezes Assets
Third Party informs "Bob is real depositor"
Alice requests a withdraw
Contracts does nothing
Bob requests a withdraw
Contract emits
ethscriptions_protocol_TransferEthscription(Bob, 0x123)
This solution works, but if the third party is not available it will be impossible for anyone to withdraw their assets. It would be preferable to have a decentralized alternative.
Reducing Contract Informational Needs
If a contract doesn't itself have a piece of information, it is not possible to deliver that information to the contract in a trustless fashion. Because of this, trustless solutions for contract escrow involve reducing the information a contract requires to make correct decisions, rather than supplying the contract with inaccessible information.
Specifically, ESIP-2 creates a mechanism for smart contracts to act in the interests of a depositor without having to know who that depositor is. Contracts achieve this through conditional transfers. Instead absolute transfers like "Send 0x123 to Alice," contracts can say "Send 0x123 to Alice, if and only if Alice deposited 0x123."
Now the potential exploit looks like this:
Alice "Deposits" id 0x123
Bob Deposits id 0x123
(Bob's deposit is real, Alice's isn't)
Alice requests a withdraw
Contracts emits
ethscriptions_protocol_TransferEthscriptionForPreviousOwner(Alice, Alice, 0x123)
Bob requests a withdraw
Contract emits
ethscriptions_protocol_TransferEthscriptionForPreviousOwner(Bob, Bob, 0x123)
With ESIP-2 the contract doesn't have to gather the information necessary to determine which of Alice and Bob's withdrawal requests are legitimate and to change its behavior accordingly.
Instead, the contract does the same thing for Alice's withdraw as it does for Bob's. However, because TransferEthscriptionForPreviousOwner
is only valid when Alice is the legitimate previous owner—which she cannot be here as her deposit is invalid—this transfer is invalid under the protocol and, like all invalid transfers, will be ignored by indexers.
The goal is to make smart contracts "dumber." Instead of smart contracts having to decide which user requests to ignore based on different user permissions, the smart contract can treat all user requests the same, knowing that the invalid requests will be filtered out at the protocol level.
Example Smart Contract
This is an example implementation of an EthscriptionsEscrower
base contract that a marketplace can inherit from.
For example, a marketplace would call something like _transferEthscription(seller, msg.sender, ethscriptionId)
in the "buy" function.
In addition to ESIP-2 it contains an additional best practice of an enforced 5 block cooldown period between transfers to account for potential indexer delays and reorgs.
Last updated