Proxy Contracts (EIP-17)
EIP-0017: Proxy Contracts
Author: anon_real
Status: Proposed
Created: 05-May-2021
Motivation
Outsourcing transaction generation to an external service/dApp can be useful or even needed in various circumstances. For example, avoiding wallet limitations to generate any transaction on behalf of the user - Ergo Assembler is designed for this purpose.
Another example is to scale dApps to be able to fulfill many requests without double-spending or data invalidation - SigmaUSD dApp can use proxy contracts to avoid bank double-spending and ERG/USD oracle data invalidation.
Key Use Cases
- Avoiding wallet limitations for transaction generation
- Scaling dApps without double-spending issues
- Protecting users from malicious activities
- Preserving dApp infrastructure integrity
Background
The idea of proxy contracts came to life with the Ergo Assembler which helped dApp developments like Ergo Auction House, ErgoUtils, and SigmaUSD web interface despite not having a wallet-bridge like MetaMask (Ethereum wallet) in the ecosystem.
During this time, the structure of proxy contracts evolved as some malicious users tried to take advantage of some minor vulnerabilities, mostly in the SigmaUSD dApp.
The Structure
In the beginning, the sole purpose of proxy contracts was to protect users from losing their funds (not to be cheated) when they outsource their assets to engage with some dApp. While the initial structure succeeded to achieve this, it proved to be not sufficient for the whole dApp infrastructure to work without malicious activities.
Examples of dApp Infrastructure Violations
- A malicious whale tried to take advantage of this simple structure by stealing UI fees from SigmaUSD web interface developers for some period of time.
- The same whale tried to prevent user's requests (minting/redeeming) from being executed by the assembler service by returning their funds as soon as funds were broadcasted in the network.
- Moreover, the whale tried to sell SigUSD/SigRSV tokens to users by imitating the bank box, taking 2.25% fee for each request which was supposed to go to the SigRSV holders (2%) and UI devs (0.25%).
Design Requirements
Generally, proxy contracts should be designed to:
- Prevent dApp developers or any other attacker from taking advantage of user's funds in any manner
- Preserve the integrity of the dApp by preventing attacks like the ones explained in the above examples
Contract Structure Example
The below contract structure is proposed as an example for SigmaUSD minting operation:
{
// dApp-specific part ensuring that user will receive what he is paying for
val properFundUsage = {
val userOut = OUTPUTS(1)
userOut.propositionBytes == fromBase64("\$userAddress") && // user must be the recipient
userOut.tokens(0)._1 == fromBase64("\$scTokenId") && // user must receive SigmaUSD
userOut.tokens(0)._2 >= \$scAmountL && // the amount of SigmaUSD must be at least what user is paying for
HEIGHT < \$timestampL // this part is always true (timestamp is the unix-timestamp at the time of the request), it will cause compiled address to differ everytime
}
// ensuring dApp integrity is preserved - any dApp specific condition to ensure designed procedures won't be violated
val UIFeeOk = OUTPUTS(2).propositionBytes == fromBase64("\$implementor") && OUTPUTS.size == 4 // UI fee must go to UI devs not any random person who assembles the transaction
val properBank = OUTPUTS(0).tokens(2)._1 == fromBase64("\$bankNFT") // the real bank box of the sigmaUSD protocol must be used so not any random person can behave as the bank box
val dAppWorksFine = properFundUsage && UIFeeOk && properBank
// in any case, whether assembler refuses to execute the request or the request fails for any reason, user must be able to get back his funds
val returnFunds = {
val total = INPUTS.fold(0L, {(x:Long, b:Box) => x + b.value}) - \$returnFee // only refund transactions's fee must be deducted from user's funds
OUTPUTS(0).value >= total && OUTPUTS(0).propositionBytes == fromBase64("\$userAddress") // user must receive the appropriate amount
&& (PK("\$assemblerNodeAddr") || HEIGHT > \$refundHeightThreshold) && // either dApp-specific node can return user's funds or some time (block) has to be passed first. This is useful for many reasons.
OUTPUTS.size == 2 // only refund box and transaction fee box is needed
}
sigmaProp(dAppWorksFine || returnFunds) // either dApp must work as it is supposed to or user's funds must be returned
}1. Proper Fund Usage
Ensuring proper usage of user's funds, i.e., user will receive what he is paying for in proper amount without anyone being able to cheat.
2. dApp Integrity
Ensuring the integrity of the dApp procedures and preventing malicious activities that could compromise the system.
3. Refund Mechanism
Ensuring that user will be refunded in any case of failures, whether assembler refuses to execute or the request fails.
Important Contract Parts
Refund Authorization
(PK("$assemblerNodeAddr") || HEIGHT > $refundHeightThreshold)Either dApp's specific node can refund the user (at any time) or some time has to be passed for refunding without any secrets involved. This part prevents malicious users from sending refunds to users, preventing user's request (minting, in this case) from being executed.
Timestamp Validation
HEIGHT < $timestampLThis will result in different address every time the contract is compiled. This is useful from the UI perspective to be able to track user's requests in an address-specific manner.
Benefits
Security
- Protects users from losing funds
- Prevents malicious activities
- Ensures proper fund usage
- Maintains dApp integrity
Scalability
- Enables dApp scaling
- Controls execution order
- Prevents double-spending
- Manages multiple requests
Additional Resources
Related Projects
- Ergo Assembler - Transaction generation service
- Ergo Auction House - Auction platform
- ErgoUtils - Utility tools
- SigmaUSD - Stablecoin protocol
Documentation
- EIP-0017 Full Specification
- Ergo Platform - Official documentation
- Ergo Core - Reference implementation