Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC 1500: Non-transferrable, configurable vouchers for free usage of dApps #1500

Closed
jaytoday opened this issue Oct 16, 2018 · 3 comments
Closed
Labels

Comments

@jaytoday
Copy link

jaytoday commented Oct 16, 2018


eip: 1500
title: Non-transferrable, configurable vouchers for free usage of dApps
author: James Levy (@jamslevy), Chance Wees (@figs999)
discussions-to: ethereum-magicians.org
status: Draft
type: Standards Track
category: ERC
created: 2018-11-05

Simple Summary

A system for facilitating sponsored, non-transferrable and configurable vouchers for dApps. This system is designed to not require any modifications from dApp contracts or front-end code.

Abstract

An on-chain system for facilitating sponsored vouchers that can be redeemed on a specified contract but cannot be transferred or sold, and can be configured to require identity verification to prevent abuse or other factors.

When paired with a gas relayer meta transaction system such as the ones designed by the meta cartel, this system allows for new users without funds in their account to not only send transactions, but to purchase collectibles, make transactions on marketplaces, and other operations requiring both gas fees and additional funding.

Redeemed voucher funds are sent as a normal transaction from the redeeming user, and support for this system does not require any front-end dApp code or the contract code used by dApps to be modified.

Motivation

The overall goal of providing a more simple and low-friction onboarding process for new users is one of the main challenges for Ethereum to substantially broaden its addressable audience. For those who do not already have cryptocurrency funds available to spend and would like to try one or more decentralized apps, the only option typically available to them is to go through a lengthy fiat onramp process where their bank details must be verified before a purchase of some initial funds can be completed.

While the use of gas relayers and meta transactions helps to reduce this friction for new users, there are a limited number of decentralized apps that only require gas fees. Many of the applications and use-cases of interest require additional funds beyond gas to acquire tokens or NFTs.

Meanwhile, dApp developers struggle to acquire new users amongst an increasingly large volume of apps to choose from.

This system helps both the new users and the app developers seeking additional ways of acquiring users, without requiring dApp developers to modify either their contract code or front-end code.

Specification

Terminology

  • Vouchers Registry is a registry contract that allows donors to provide funds used for vouchers and to configure any additional parameters to be used for their donated voucher funds. It also facilitates the redemption of voucher funds by users, and handles a recommended whitelisting process for users.

  • Vouchers User is a user contract containing functions defined by this EIP required for the redemption of voucher funds, that may also be interoperable with existing user contract standards such as ERC 725 and ERC 1077.


Vouchers Registry

The Vouchers Registry contract should contain several mappings that are used for maintaining information about donated and redeemed voucher funds, as well as a recommended addressIdentityVerified mapping for whitelisting users:

contract VouchersRegistry is Ownable{
    
    mapping(bytes32 => uint) public contractVouchersDonorBalance;
    mapping(bytes32 => uint) public contractVouchersAddressRedeemed;
    mapping(bytes32 => uint) public contractVouchersRedeemablePerUser;
    mapping(bytes32 => uint) public contractVouchersFunctionData;
    mapping(address => uint) public addressIdentityVerified;

}

The Vouchers Registry contract should also contain several functions that are used for donating and redeeming voucher funds and whitelisting users:

addContractVouchers

This payable function is used by voucher donors to provide funds to be used for vouchers. The function accepts a contractAddress parameter specifying for which contract the donated funds should be used, as well as a redeemablePerUser parameter specifying how much funds (in wei) should be redeemable by each user for the specified set of voucher funds. It

 function addContractVouchers(address contractAddress, uint redeemablePerUser, bytes32 voucherFunctionData) 

withdrawContractVouchers

This function allows donors to withdraw any unspent funds they have previously donated that have not already been spent. It accepts acontractAddress parameter that should match the same parameter specified in addContractVouchers and a withdrawAmount parameter specifying how much of the previously donated funds to withdraw.

function withdrawContractVouchers(address contractAddress, uint withdrawAmount)

redeemContractVouchers

This function is called by the Vouchers User contract when voucher funds are being redeemed by a user. It checks that the requested funds are available, do not exceed the available allocated amount for the requesting user, and the balance of voucher funds redeemed by the requesting user is updated to the contract's storage, with a small additional gas fee included.

function redeemContractVouchers(address contractAddress, address donorAddress, uint redeemAmount, bytes32 voucherFunctionData)

This function calls the forwardRedeemedVouchers function in the Vouchers User contract:

userAddress.forwardRedeemedVouchers.value(redeemAmount)(contractAddress, voucherFunctionData);

setAddressIdentityVerified

This function contains functionality for having the administrator of the vouchers registry specify users that have gone through an identity verification process that is recommended to avoid abuse of the system via "sybil attacks". The details of this off-chain process are left up to the administrator of the vouchers registry.

function setAddressIdentityVerified(address userAddress, uint verificationToken)

Additional functionality may be added by the Vouchers Registry administrator, such as requiring participating contracts to be whitelisted.


Vouchers User

The Vouchers User contract contains two functions that are required for the redemption of voucher funds. In the most simple example, it uses an authentication model where authenticated actions are limited to the contract owner. However, the authentication model can be modified to allow for patterns such as meta transactions that may result in additional UX improvements.

requestContractVouchers

This function accepts a contractAddress parameter and donorAddress parameter that are used to determine which voucher funds the user is redeeming. A future iteration of this system may potentially eliminate the need for the user to specify a donorAddress parameter.

function requestContractVouchers(address contractAddress, address donorAddress, uint redeemAmount, bytes32 voucherFunctionData)

The function calls the redeemContractVouchers function of the Voucher Registry contract:

_registry.redeemContractVouchers(contractAddress, donorAddress, redeemAmount, voucherFunctionData);

That function, in turn calls forwardRedeemedVouchers function of the Voucher User contract.

forwardRedeemedVouchers

This function is called from the redeemContractVouchers function of the Voucher Registry contract.

function forwardRedeemedVouchers(address contractAddress, bytes32 voucherFunctionData) 

Implementation

Voucher Registry Contract

pragma solidity ^0.4.24;

import './openzeppelin-solidity/contracts/ownership/Ownable.sol';
import './openzeppelin-solidity/contracts/math/SafeMath.sol';
import './VouchersUser.sol';

contract VouchersRegistry is Ownable{
    
    string constant SAFETY_CHECK_PASSED = "safetyCheckPassed";
    string constant SAFETY_CHECK_FAILED = "safetyCheckFailed";
    
    mapping(address => uint) public safetyCheckPassedContracts;
    mapping(address => uint) public safetyCheckFailedContracts;
    
    mapping(bytes32 => uint) public contractVouchersDonorBalance;
    mapping(bytes32 => uint) public contractVouchersAddressRedeemed;
    mapping(bytes32 => uint) public contractVouchersRedeemablePerUser;
    mapping(bytes32 => bytes32) public contractVouchersFunctionData;
    mapping(address => uint) public addressIdentityVerified;
    
    uint redemptionGasFee = 1000000;
    
    function setContractStatus(address contractAddress, string status, uint checkVersion) public onlyOwner {
        require(checkVersion > 0);
        
        if(stringsEquivelant(status, SAFETY_CHECK_PASSED))
            SetSafetyCheckPassed(contractAddress,checkVersion);
        else if(stringsEquivelant(status, SAFETY_CHECK_FAILED))
            SetSafetyCheckFailed(contractAddress,checkVersion);
    }
    
    function SetSafetyCheckPassed(address contractAddress, uint checkVersion) internal{
        safetyCheckPassedContracts[contractAddress] = checkVersion;
        if(safetyCheckFailedContracts[contractAddress] != 0)
            safetyCheckFailedContracts[contractAddress] = 0;
    }
    
    function SetSafetyCheckFailed(address contractAddress, uint checkVersion) internal{
        safetyCheckFailedContracts[contractAddress] = checkVersion;
        if(safetyCheckPassedContracts[contractAddress] != 0)
            safetyCheckPassedContracts[contractAddress] = 0;
    }
	
	function setAddressIdentityVerified(address userAddress, uint verificationToken) public onlyOwner {
		addressIdentityVerified[userAddress] = verificationToken;
	}
    
    function stringsEquivelant (string a, string b) internal pure returns (bool){
       return keccak256(bytes(a)) == keccak256(bytes(b));
    }
    
    event AddressNotPassedSafetyCheckRefund(address refundee, uint refundAmount);
    
    function addContractVouchers(address contractAddress, uint redeemablePerUser, bytes32 voucherFunctionData) public payable {
        require(safetyCheckPassedContracts[contractAddress] > 0);
        
        address donorAddress = msg.sender;
        bytes32 voucherKey = getVoucherKey(donorAddress, contractAddress);
        
        uint currentBalance = contractVouchersDonorBalance[voucherKey];
        contractVouchersDonorBalance[voucherKey] = SafeMath.add(currentBalance, msg.value);
        
        contractVouchersRedeemablePerUser[voucherKey] = redeemablePerUser;
        if(voucherFunctionData != 0)
            contractVouchersFunctionData[voucherKey] = voucherFunctionData;
    }
    
    function withdrawContractVouchers(address contractAddress, uint withdrawAmount) public {
        address donorAddress = msg.sender;
        bytes32 voucherKey = getVoucherKey(donorAddress, contractAddress);
        uint currentBalance = contractVouchersDonorBalance[voucherKey];
        
        require(currentBalance >= withdrawAmount && withdrawAmount > 0);
        
        contractVouchersDonorBalance[voucherKey] = SafeMath.sub(currentBalance, withdrawAmount);
        msg.sender.transfer(withdrawAmount);
    }
	
	function redeemContractVouchers(address contractAddress, address donorAddress, uint redeemAmount, bytes32 voucherFunctionData) public {
		VouchersUser userAddress = VouchersUser(msg.sender);
		require(addressIdentityVerified[userAddress] > 0);
		
		uint gasCost = SafeMath.mul(redemptionGasFee, tx.gasprice);
		uint requiredAmount = SafeMath.add(redeemAmount, gasCost);
		
		bytes32 voucherKey = getVoucherKey(donorAddress, contractAddress);
		uint donorBalance = contractVouchersDonorBalance[voucherKey];
		require(donorBalance >= requiredAmount);
		
		bytes32 userKey = getUserKey(donorAddress, contractAddress, userAddress); 
		uint totalRedemption = SafeMath.add(redeemAmount, contractVouchersAddressRedeemed[userKey]);
		
		require(totalRedemption <= contractVouchersRedeemablePerUser[voucherKey]);
		contractVouchersAddressRedeemed[userKey] = totalRedemption;
		contractVouchersDonorBalance[voucherKey] = SafeMath.sub(donorBalance,requiredAmount);

		if(contractVouchersFunctionData[voucherKey] > 0)
			voucherFunctionData = contractVouchersFunctionData[voucherKey];
        		
		userAddress.forwardRedeemedVouchers.value(redeemAmount)(contractAddress, voucherFunctionData);
	}
	
	function getVoucherKey(address donorAddress, address contractAddress) public pure returns(bytes32) {
		return keccak256(abi.encodePacked(donorAddress, contractAddress));
	}

	function getUserKey(address donorAddress, address contractAddress, address userAddress) public pure returns(bytes32) {
		return keccak256(abi.encodePacked(donorAddress, contractAddress, userAddress));
	}		
}

Voucher User Contract

In this example, direct authentication is used where transactions requesting the redemption of voucher funds must be made by the Voucher User contract owner.

pragma solidity ^0.4.24;

import './openzeppelin-solidity/contracts/ownership/Ownable.sol';
import './openzeppelin-solidity/contracts/math/SafeMath.sol';
import './relevant-community/contracts/BondingCurve.sol';
import './VouchersRegistry.sol';

contract VouchersUser is Ownable{
	VouchersRegistry _registry;

	constructor(VouchersRegistry registry) public {
		require(uint256(registry) != 0);
		_registry = registry;
	}
	
	function requestContractVouchers(address contractAddress, address donorAddress, uint redeemAmount, bytes32 voucherFunctionData) public onlyOwner 
	{
		_registry.redeemContractVouchers(contractAddress, donorAddress, redeemAmount, voucherFunctionData);
	}
	
	function forwardRedeemedVouchers(address contractAddress, bytes32 voucherFunctionData) public payable {
		require(contractAddress.call.value(msg.value)(voucherFunctionData));
	}
}

Roadmap

  • Variation using meta transaction authentication instead of direct authentication for the Voucher User contract.

  • Variation where the user does not need to specify the donorAddress, and are automatically matched with the best available voucher funds to use for a specified contract.

Copyright

Copyright and related rights waived via CC0.

@jaytoday jaytoday changed the title Free credits system for identity contracts ERC-1500: Free credits system for identity contracts Oct 16, 2018
@jaytoday jaytoday changed the title ERC-1500: Free credits system for identity contracts ERC-1500: Non-transferrable, configurable vouchers for dApps Nov 1, 2018
@jaytoday jaytoday changed the title ERC-1500: Non-transferrable, configurable vouchers for dApps ERC-1500: Non-transferrable, configurable vouchers for free usage of dApps Nov 1, 2018
@pb25193
Copy link

pb25193 commented Nov 5, 2018

Hey, james.
We posted EIP 1261 some days ago. We are currently using EIP 1261 for doing what is described in this post. We are building infrastructure presently that gives you a GUI to manage the membership in the contracts that we deploy. EIP 1261 tokens are non transferable, revocable, and are used as permissioning tools. Originally designed for governance apps, it can be used for dapp usage permissioning as well. We will be releasing a dapp called Vault, a crowdfunding platform, which uses EIP 1261 to enable 'sign ups'

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1261.md

@jaytoday jaytoday changed the title ERC-1500: Non-transferrable, configurable vouchers for free usage of dApps ERC 1500: Non-transferrable, configurable vouchers for free usage of dApps Nov 5, 2018
@github-actions
Copy link

github-actions bot commented Dec 4, 2021

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Dec 4, 2021
@github-actions
Copy link

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants