rooch-network / rooch

VApp Container with Move Language for Bitcoin ecosystem
https://rooch.network
Apache License 2.0
158 stars 83 forks source link

[GasFree] The contract pay gas to end users #839

Open stevenlaw123 opened 1 year ago

stevenlaw123 commented 1 year ago

After compiling a Move module, analyze all the functions. If a function has the #[pay_gas] attribute, when a user calls a contract function within this module and needs to pay gas, the MoveVM will invoke the function decorated with #[pay_gas]. If this function returns true, the gas that the user should have paid will be deducted from the account associated with the current module.

A function that can be decorated with #[pay_gas] follows the format below:

module test_module::0x42 {
    #[pay_gas]
    public fun gas_checking(gas: u64, user_addresss: addresss): bool {
        true
    }

    public fun f1(signer: &signer) {

    }
}

When a user calls m::0x42::f1(), after executing the m::0x42::f1() function, the MoveVM will calculate the gas consumed by this function and the calling address. Then, the MoveVM will call the gas_checking(gas: u64, user_addresss: addresss): bool function and pass in the gas consumed by m::0x42::f1() and the calling address. The MoveVM will receive the return value of gas_checking. If it is true, gas will be deducted from the account of the test_module module.

For functions that replace user gas payment within a module, the following rules apply:

  1. The function name is not restricted.
  2. The function's parameters and return value must be func_name(gas: u64, user_addresss: addresss): bool.
  3. Functions decorated with #[pay_gas] can only appear once within the module.

The implementation approach is as follows:

  1. During compilation, check if the module has functions decorated with #[pay_gas]. If so, check if the number and types of parameters and return values of the functions are valid. If not, the compiler throws an error.
  2. If the checks pass, record the found gas payment function within the current module. If a second function with the same type is found, the compiler throws an error.
  3. If the above checks pass, record the name of the gas payment function in the module's Metadata.
  4. After executing a transaction, the MoveVM reads the Metadata of the corresponding module and retrieves the gas payment function information from it.
  5. Encapsulate a transaction for gas payment, with the parameters being the gas consumed by the user's invocation of the contract function and the user's address.
  6. The MoveVM executes the transaction for the gas payment function. If the return value is true, gas will be deducted from the module's account. Otherwise, it will still be deducted from the address that called the contract function.
jolestar commented 12 months ago

I also designed an example for the meetup show.

module mygame::play{

    public fun gas_validate(ctx: &StorageContext) :bool{
        //if the contract pays gas for the current tx, return true
        true
    }

    fun gas_charge_post(ctx: &mut StorageContext, gas_used: u256){
        // records gas pay amount for users
    }

    #[gas_free(gas_validate="mygame::gas_validate",gas_charge_post="mygame::gas_charge_update")]
    public entry play(ctx: &mut StorageContext, sender: &signer){
    }

}
  1. gas_free means the contract account provides the gas.
  2. gas_validate references a function. If the function returns true, the gas will be paid by the contract account, otherwise, it will be paid by the user account.
yubing744 commented 12 months ago

[gas-payer(function-id="0x5::mygame::play")]

jolestar commented 7 months ago

The follow refactor tracked via #1334 1334