ERGO

Proxy Contracts (EIP-17)

EIP-0017: Proxy Contracts

Author: anon_real

Status: Proposed

Created: 05-May-2021

License: CC0

Forking: not needed

Source: EIP-0017

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 < $timestampL

This 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

Documentation