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

Gas Consumption Analysis on Bridge Solidity Contracts #842

Open
0xmovses opened this issue Nov 12, 2024 · 4 comments
Open

Gas Consumption Analysis on Bridge Solidity Contracts #842

0xmovses opened this issue Nov 12, 2024 · 4 comments
Labels

Comments

@0xmovses
Copy link
Contributor

0xmovses commented Nov 12, 2024

initiateBridgeTransfer is consuming around 400k units of gas, this is far too high for what the function is doing.

There could be several culprits, the struct and its size (fields). The way we operate on the mapping bridgeTransferIds or, something else.

Do an analysis of gas consumption on the solidity contracts for the bridge in main. You can do this using forge test --gas-report.

My suspicion is this line causing the high gas cosumption

bridgeTransferId = keccak256(abi.encodePacked(originator, recipient, hashLock, initiatorTimeLockDuration, block.timestamp, nonce++));

Too much salt, if we take param number down to three, it should be several degrees less consuming. We need nonce tho as this garantees uniqueness of bridgeTransferId.

On this issue, paste your findings and suggest what gas optimisations can be made.

@apenzk
Copy link

apenzk commented Nov 12, 2024

is there an easy way to run a test func and extract how much gas was consumed (e.g. plot it while running test)
you would write a forge solidity test, that simply calls the one function

i propose to write such a function. the test should fail, based on some fixed inputs that the gas is not cray high. this should be feasible, because the test is deterministic. it would be a control mechanism that checks that no high-fee consuming functionality is introduced by error

@franck44
Copy link

@0xmovses

The cost of keccak256 is 30 + (6 * size in words of the arguments).
You can find the specification of the keccak256 cost function here.
The constants used in the previous function are defined here.

So overall, if I am correct the cost of hashing 1K bytes, is 30 + (6 * 1000/32) = 217.5gas (a word in the EVM is 32bytes, 256bits).

In contrast writing to permanent storage is very expensive. It is 20000 gas per word.

So the following lines

bridgeTransfers[bridgeTransferId] = BridgeTransfer({
            amount: moveAmount,
            originator: originator,
            recipient: recipient,
            hashLock: hashLock,
            timeLock: block.timestamp + initiatorTimeLockDuration,
            state: MessageState.INITIALIZED
        });

probably incur ~100K gas for the write to storage.

@Primata
Copy link
Contributor

Primata commented Nov 12, 2024

| src/NativeBridgeCounterpartyMOVE.sol:NativeBridgeCounterpartyMOVE contract |                 |        |        |        |         |
|----------------------------------------------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost                                                            | Deployment Size |        |        |        |         |
| 676213                                                                     | 2909            |        |        |        |         |
| Function Name                                                              | min             | avg    | median | max    | # calls |
| abortBridgeTransfer                                                        | 28121           | 28121  | 28121  | 28121  | 1       |
| bridgeTransfers                                                            | 1327            | 1327   | 1327   | 1327   | 3       |
| completeBridgeTransfer                                                     | 85474           | 85474  | 85474  | 85474  | 1       |
| initialize                                                                 | 92849           | 92849  | 92849  | 92849  | 3       |
| lockBridgeTransfer                                                         | 132900          | 132900 | 132900 | 132900 | 3       |


| src/NativeBridgeInitiatorMOVE.sol:NativeBridgeInitiatorMOVE contract |                 |        |        |        |         |
|----------------------------------------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost                                                      | Deployment Size |        |        |        |         |
| 798205                                                               | 3473            |        |        |        |         |
| Function Name                                                        | min             | avg    | median | max    | # calls |
| bridgeTransfers                                                      | 1258            | 1258   | 1258   | 1258   | 2       |
| completeBridgeTransfer                                               | 30670           | 30670  | 30670  | 30670  | 1       |
| initialize                                                           | 95115           | 95115  | 95115  | 95115  | 6       |
| initiateBridgeTransfer                                               | 204253          | 204253 | 204253 | 204253 | 6       |
| poolBalance                                                          | 2306            | 2306   | 2306   | 2306   | 3       |
| refundBridgeTransfer                                                 | 2522            | 37544  | 37544  | 72567  | 2       |
| setCounterpartyAddress                                               | 24725           | 24725  | 24725  | 24725  | 3       |
| withdrawMOVE                                                         | 42804           | 42804  | 42804  | 42804  | 1       |

You can see that the struct storage calls are the most expensive ones.

@franck44
Copy link

franck44 commented Nov 13, 2024

@0xPrimata Thanks a lot for this breakdown.

The minimum gas cost of any transaction is ~21700 gas units.
In the struct(s) we have 6 fields, each one needs one storage slot (256 bits).
As I mentioned before, writing a slot in permanent storage uses ~20000 gas units.
As a result, storing the structs costs ~120K gas, so in the counterpart, 132K is not too bad.
And in the initiator, 200K looks OK too.

For reference, gasNow, at 4pm Sydney time Nov 13, 130K gas units cost ~$10 for a fast Tx, and $6 for a standard.

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

4 participants