-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce gas.EstimateProvider used by verifyReport for gas limiting. (…
…#56)
- Loading branch information
Showing
10 changed files
with
368 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Package evm provides an EVM implementation to the gas.EstimateProvider interface. | ||
// TODO: Move this package into the EVM repo, chainlink-ccip should be chain agnostic. | ||
package evm | ||
|
||
import ( | ||
"math" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" | ||
) | ||
|
||
const ( | ||
EvmAddressLengthBytes = 20 | ||
EvmWordBytes = 32 | ||
CalldataGasPerByte = 16 | ||
TokenAdminRegistryWarmupCost = 2_500 | ||
TokenAdminRegistryPoolLookupGas = 100 + // WARM_ACCESS_COST TokenAdminRegistry | ||
700 + // CALL cost for TokenAdminRegistry | ||
2_100 // COLD_SLOAD_COST loading the pool address | ||
SupportsInterfaceCheck = 2600 + // because the receiver will be untouched initially | ||
30_000*3 // supportsInterface of ERC165Checker library performs 3 static-calls of 30k gas each | ||
PerTokenOverheadGas = TokenAdminRegistryPoolLookupGas + | ||
SupportsInterfaceCheck + | ||
200_000 + // releaseOrMint using callWithExactGas | ||
50_000 // transfer using callWithExactGas | ||
RateLimiterOverheadGas = 2_100 + // COLD_SLOAD_COST for accessing token bucket | ||
5_000 // SSTORE_RESET_GAS for updating & decreasing token bucket | ||
ConstantMessagePartBytes = 10 * 32 // A message consists of 10 abi encoded fields 32B each (after encoding) | ||
ExecutionStateProcessingOverheadGas = 2_100 + // COLD_SLOAD_COST for first reading the state | ||
20_000 + // SSTORE_SET_GAS for writing from 0 (untouched) to non-zero (in-progress) | ||
100 //# SLOAD_GAS = WARM_STORAGE_READ_COST for rewriting from non-zero (in-progress) to non-zero (success/failure) | ||
) | ||
|
||
type EstimateProvider struct { | ||
} | ||
|
||
// CalculateMerkleTreeGas estimates the merkle tree gas based on number of requests | ||
func (gp EstimateProvider) CalculateMerkleTreeGas(numRequests int) uint64 { | ||
if numRequests == 0 { | ||
return 0 | ||
} | ||
merkleProofBytes := (math.Ceil(math.Log2(float64(numRequests))))*32 + (1+2)*32 // only ever one outer root hash | ||
return uint64(merkleProofBytes * CalldataGasPerByte) | ||
} | ||
|
||
// return the size of bytes for msg tokens | ||
func bytesForMsgTokens(numTokens int) int { | ||
// token address (address) + token amount (uint256) | ||
return (EvmAddressLengthBytes + EvmWordBytes) * numTokens | ||
} | ||
|
||
// CalculateMessageMaxGas computes the maximum gas overhead for a message. | ||
func (gp EstimateProvider) CalculateMessageMaxGas(msg ccipocr3.Message) uint64 { | ||
numTokens := len(msg.TokenAmounts) | ||
var data []byte = msg.Data | ||
dataLength := len(data) | ||
|
||
// TODO: parse max gas from ExtraArgs. | ||
|
||
messageBytes := ConstantMessagePartBytes + | ||
bytesForMsgTokens(numTokens) + | ||
dataLength | ||
|
||
messageCallDataGas := uint64(messageBytes * CalldataGasPerByte) | ||
|
||
// Rate limiter only limits value in tokens. It's not called if there are no | ||
// tokens in the message. The same goes for the admin registry, it's only loaded | ||
// if there are tokens, and it's only loaded once. | ||
rateLimiterOverhead := uint64(0) | ||
adminRegistryOverhead := uint64(0) | ||
if numTokens >= 1 { | ||
rateLimiterOverhead = RateLimiterOverheadGas | ||
adminRegistryOverhead = TokenAdminRegistryWarmupCost | ||
} | ||
|
||
return messageCallDataGas + | ||
ExecutionStateProcessingOverheadGas + | ||
SupportsInterfaceCheck + | ||
adminRegistryOverhead + | ||
rateLimiterOverhead + | ||
PerTokenOverheadGas*uint64(numTokens) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package evm | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" | ||
) | ||
|
||
func Test_calculateMessageMaxGas(t *testing.T) { | ||
type args struct { | ||
dataLen int | ||
numTokens int | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want uint64 | ||
}{ | ||
{ | ||
name: "base", | ||
args: args{dataLen: 5, numTokens: 2}, | ||
want: 822_264, | ||
}, | ||
{ | ||
name: "large", | ||
args: args{dataLen: 1000, numTokens: 1000}, | ||
want: 346_477_520, | ||
}, | ||
{ | ||
name: "overheadGas test 1", | ||
args: args{dataLen: 0, numTokens: 0}, | ||
want: 119_920, | ||
}, | ||
{ | ||
name: "overheadGas test 2", | ||
args: args{dataLen: len([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), numTokens: 1}, | ||
want: 475_948, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
msg := ccipocr3.Message{ | ||
Data: make([]byte, tt.args.dataLen), | ||
TokenAmounts: make([]ccipocr3.RampTokenAmount, tt.args.numTokens), | ||
} | ||
ep := EstimateProvider{} | ||
got := ep.CalculateMessageMaxGas(msg) | ||
fmt.Println(got) | ||
assert.Equalf(t, tt.want, got, "calculateMessageMaxGas(%v, %v)", tt.args.dataLen, tt.args.numTokens) | ||
}) | ||
} | ||
} | ||
|
||
// TestCalculateMaxGas is taken from the ccip repo where the CalculateMerkleTreeGas and CalculateMessageMaxGas values | ||
// are combined to one function. | ||
func TestCalculateMaxGas(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
numRequests int | ||
dataLength int | ||
numberOfTokens int | ||
want uint64 | ||
}{ | ||
{ | ||
name: "maxGasOverheadGas 1", | ||
numRequests: 6, | ||
dataLength: 0, | ||
numberOfTokens: 0, | ||
want: 122992, | ||
}, | ||
{ | ||
name: "maxGasOverheadGas 2", | ||
numRequests: 3, | ||
dataLength: len([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), | ||
numberOfTokens: 1, | ||
want: 478508, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
msg := ccipocr3.Message{ | ||
Data: make([]byte, tt.dataLength), | ||
TokenAmounts: make([]ccipocr3.RampTokenAmount, tt.numberOfTokens), | ||
} | ||
ep := EstimateProvider{} | ||
|
||
gotTree := ep.CalculateMerkleTreeGas(tt.numRequests) | ||
gotMsg := ep.CalculateMessageMaxGas(msg) | ||
fmt.Println("want", tt.want, "got", gotTree+gotMsg) | ||
assert.Equal(t, tt.want, gotTree+gotMsg) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package gas | ||
|
||
import "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" | ||
|
||
type EstimateProvider interface { | ||
CalculateMerkleTreeGas(numRequests int) uint64 | ||
CalculateMessageMaxGas(msg ccipocr3.Message) uint64 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.