smart-contracts
: gas forwarding
The Nil.sol
contract in the smart-contracts
package provides the means to handle gas forwarding.
Gas forwarding applies to cases where an async call to one contract triggers a chain of async calls to other contracts:
Consider this flow:
- The smart account sets
FeeCredit
to500_000
and asynchronously calls Contract A - The transaction is executed for
200_000
, leaving300_000
tokens unused - As part of executing the initial transaction from the smart account, Contract A must send async calls to Contract B and Contract C requiring at least
150_000
and350_000
FeeCredit
, respectively - Contract A must decide how it will pay for these calls
=nil; provides five possible options on how Contract A can handle forwarding tokens for executing subsequent calls. These options are set via the uint8 forwardKind
argument when using asyncCall()
:
function asyncCall(
...
address bounceTo,
uint feeCredit,
uint8 forwardKind,
...
)
No gas forwarding
The smart account can set some FeeCredit
and send some tokens to Contract A:
In the above flow:
- The smart account sets
FeeCredit
to200_000
and asynchronously calls Contract A. The smart account also sends500_000
tokens asValue
- The transaction is executed for
100_000
, leaving another100_000
ofFeeCredit
available - Instead of forwarding the leftover
FeeCredit
, Contract A pays for async calls to Contract B and Contract C from its balance
This is how Contract A calls Contract B:
function callContractB(
Nil.asyncCall(
CONTRACT_B_ADDRESS,
...
150000,
Nil.FORWARD_NONE,
...
);
)
And Contract C:
function callContractC(
Nil.asyncCall(
CONTRACT_C_ADDRESS,
...
350000,
Nil.FORWARD_NONE,
...
);
)
Forwarding by absolute value
The smart account only sets FeeCredit
and does not send any tokens to Contract A:
In the above flow:
- The smart account sets
FeeCredit
to600_000
and asynchronously calls Contract A. No tokens are sent to Contract A directly - The transaction is executed for
100_000
, leaving another500_000
ofFeeCredit
available - Contract A forwards the leftover
FeeCredit
to pay for execution of Contract B (150_000
) and Contract C (350_000
)
Here is how Contract A calls Contract B:
function callContractB(
Nil.asyncCall(
CONTRACT_B_ADDRESS,
...
150000,
Nil.FORWARD_VALUE,
...
);
)
And Contract C:
function callContractC(
Nil.asyncCall(
CONTRACT_C_ADDRESS,
...
350000,
Nil.FORWARD_VALUE,
...
);
)
Forwarding by percentage
Similarly to forwarding by value, the smart account only sets FeeCredit
and does not send any tokens to Contract A:
The basic flow is also similar to forwarding by value:
- The smart account sets
FeeCredit
to600_000
and asynchronously calls Contract A. No tokens are sent to Contract A directly - The transaction is executed for
100_000
, leaving another500_000
ofFeeCredit
available - Contract A forwards the leftover
FeeCredit
to pay for execution of Contract B (150_000
) and Contract C (500_000
)
There is one major difference between forwarding by percentage and forwarding by value, and it is in how Contract A calls other contracts. Contract B:
function callContractB(
Nil.asyncCall(
CONTRACT_B_ADDRESS,
...
30,
Nil.FORWARD_PERCENTAGE,
...
);
)
Contract C:
function callContractC(
Nil.asyncCall(
CONTRACT_C_ADDRESS,
...
70,
Nil.FORWARD_PERCENTAGE,
...
);
)
Instead of specifying absolute values in the feeCredit
argument, Contract A sets percentages of the leftover FeeCredit
it sends to Contract B and Contract C.
Forwarding by equal split
Forwarding by equal split is the default option if the forwardKind
argument is not specified when calling asyncCall()
.
The smart account only sets FeeCredit
and does not send any tokens to Contract A:
In the above flow:
- The smart account sets
FeeCredit
to600_000
and asynchronously calls Contract A. No tokens are sent to Contract A directly - The transaction is executed for
100_000
, leaving another500_000
ofFeeCredit
available - Contract A forwards the leftover
FeeCredit
to pay for execution of Contract B (250_000
) and Contract C (250_000
)
Note that Contract A splits the leftover FeeCredit
equally between Contract B and Contract C. Here is how it calls Contract B:
function callContractB(
Nil.asyncCall(
CONTRACT_B_ADDRESS,
...
Nil.FORWARD_REMAINING,
...
);
)
Contract C:
function callContractC(
Nil.asyncCall(
CONTRACT_C_ADDRESS,
...
Nil.FORWARD_PERCENTAGE,
...
);
)
The feeCredit
argument is not specified in both uses of asyncCall()
. As FORWARD_PERCENTAGE
is the default option, it can be omitted as well.
Mixed forwarding
Contract A can also specify several different types of forwarding when calling other contracts:
In the above flow:
- The smart account sets
FeeCredit
to1_000_000
and asynchronously calls Contract A. The smart account also sends300_000
to the balance of Contract A - The transaction is executed for
100_000
, leaving another900_000
ofFeeCredit
available - Contract A sends its balance to pay for execution of Contract B (
300_000
), no gas is forwarded - Contract A forwards some of its leftover gas to pay for execution of Contract C (
350_000
), gas is forwarded by absolute value - Contract A forwards some of its leftover gas to pay for the execution of Contract D (
300_000
), gas is forwarded by percentage - Contract A forwards the remaining gas by equal split to Contract E and Contract F
When value and percentage forwarding are used in mixed forwarding, value forwarding calculated first. For example, if there is 900_000
leftover FeeCredit
and 300_000
is forwarded by value to other contracts while 30% is forwarded by percentage, thia 30% would equal 200_000
instead of 300_000
.
Conversely, equal split forwarding is always calculated last. In the above example, only 400_000
leftover FeeCredit
will be left after calculating value and percentage forwarding. This amount will be split equally among the remaining contracts to be called.
Here is how Contract A calls Contract B:
function callContractB(
Nil.asyncCall(
CONTRACT_B_ADDRESS,
...
300000,
Nil.FORWARD_NONE,
...
);
)
Contract C:
function callContractC(
Nil.asyncCall(
CONTRACT_C_ADDRESS,
...
350000,
Nil.FORWARD_VALUE,
...
);
)
Contract D:
function callContractD(
Nil.asyncCall(
CONTRACT_D_ADDRESS,
...
30,
Nil.FORWARD_PERCENTAGE,
...
);
)
Contract E:
function callContractE(
Nil.asyncCall(
CONTRACT_E_ADDRESS,
...
Nil.FORWARD_REMAINING,
...
);
)
Contract F:
function callContractF(
Nil.asyncCall(
CONTRACT_F_ADDRESS,
...
Nil.FORWARD_REMAINING,
...
);
)