Celo's op-geth
diff:
ignored:
+15432
-579
+738
-392
This is an overview of the changes in Celo’s op-geth
implementation,
a fork of Optimism’s op-geth
.
For differences between the base op-geth
and go-ethereum
, check out Optimism’s
fork-diff overview of changes.
Celo-specific features
+8082
-282
Celo token duality
+312
-38
The Celo token is both the native token and ERC-20 compatible.
diff --git op-geth/core/vm/celo_contracts.go Celo/core/vm/celo_contracts.go
new file mode 100644
index 0000000000000000000000000000000000000000..d16a7571c6a229d3123dfdf4293d5371d2b5faab
--- /dev/null
+++ Celo/core/vm/celo_contracts.go
@@ -0,0 +1,105 @@
+package vm
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/contracts/addresses"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+type CeloPrecompiledContract interface {
+ RequiredGas(input []byte) uint64 // RequiredGas calculates the contract gas use
+ Run(input []byte, ctx *celoPrecompileContext) ([]byte, error) // Run runs the precompiled contract
+}
+
+type wrap struct {
+ PrecompiledContract
+}
+
+func (pw *wrap) Run(input []byte, ctx *celoPrecompileContext) ([]byte, error) {
+ return pw.PrecompiledContract.Run(input)
+}
+
+type celoPrecompileContext struct {
+ *BlockContext
+ *params.Rules
+
+ caller common.Address
+ evm *EVM
+}
+
+func NewContext(caller common.Address, evm *EVM) *celoPrecompileContext {
+ return &celoPrecompileContext{
+ BlockContext: &evm.Context,
+ Rules: &evm.chainRules,
+ caller: caller,
+ evm: evm,
+ }
+}
+
+func celoPrecompileAddress(index byte) common.Address {
+ celoPrecompiledContractsAddressOffset := byte(0xff)
+ return common.BytesToAddress(append([]byte{0}, (celoPrecompiledContractsAddressOffset - index)))
+}
+
+func (ctx *celoPrecompileContext) IsCallerCeloToken() (bool, error) {
+ tokenAddress := addresses.GetAddressesOrDefault(ctx.evm.ChainConfig().ChainID, addresses.MainnetAddresses).CeloToken
+
+ return tokenAddress == ctx.caller, nil
+}
+
+// Native transfer contract to make CELO ERC20 compatible.
+type transfer struct{}
+
+func (c *transfer) RequiredGas(input []byte) uint64 {
+ return params.CallValueTransferGas
+}
+
+func (c *transfer) Run(input []byte, ctx *celoPrecompileContext) ([]byte, error) {
+ if isCeloToken, err := ctx.IsCallerCeloToken(); err != nil {
+ return nil, err
+ } else if !isCeloToken {
+ return nil, fmt.Errorf("unable to call transfer from unpermissioned address")
+ }
+
+ // input is comprised of 3 arguments:
+ // from: 32 bytes representing the address of the sender
+ // to: 32 bytes representing the address of the recipient
+ // value: 32 bytes, a 256 bit integer representing the amount of CELO to transfer
+ // 3 arguments x 32 bytes each = 96 bytes total input
+ if len(input) != 96 {
+ return nil, ErrInputLength
+ }
+
+ // The from parameter should always be set to the caller of the
+ // function calling the precompile (tx.sender in Solidity). Reasons why
+ // we have to pass that into the precompile from outside:
+ // * We can't use ctx.caller because that is always the CELO token
+ // * We can't use ctx.evm.Origin because that would limit usage to EOA accounts
+ // * The real value we could use is the caller's caller, which is not readily available
+ from := common.BytesToAddress(input[0:32])
+ to := common.BytesToAddress(input[32:64])
+
+ var parsed bool
+ value, parsed := math.ParseBig256(hexutil.Encode(input[64:96]))
+ if !parsed {
+ return nil, fmt.Errorf("Error parsing transfer: unable to parse value from " + hexutil.Encode(input[64:96]))
+ }
+ valueU256, overflow := uint256.FromBig(value)
+ if overflow {
+ return nil, fmt.Errorf("Error parsing transfer: value overflow")
+ }
+
+ // Fail if we're trying to transfer more than the available balance
+ if !ctx.CanTransfer(ctx.evm.StateDB, from, valueU256) {
+ return nil, ErrInsufficientBalance
+ }
+
+ ctx.Transfer(ctx.evm.StateDB, from, to, valueU256)
+
+ return nil, nil
+}
diff --git op-geth/core/vm/celo_contracts_test.go Celo/core/vm/celo_contracts_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b98ba1dd559c041b1f2c8ce2a34433ef5cd485a
--- /dev/null
+++ Celo/core/vm/celo_contracts_test.go
@@ -0,0 +1,120 @@
+package vm
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/contracts/addresses"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+ "golang.org/x/crypto/sha3"
+)
+
+func makeTestHeaderHash(number *big.Int) common.Hash {
+ preimage := append([]byte("fakeheader"), common.LeftPadBytes(number.Bytes()[:], 32)...)
+ return common.Hash(sha3.Sum256(preimage))
+}
+
+func makeTestHeader(number *big.Int) *types.Header {
+ return &types.Header{
+ ParentHash: makeTestHeaderHash(new(big.Int).Sub(number, common.Big1)),
+ Number: number,
+ GasUsed: params.DefaultGasLimit / 2,
+ Time: number.Uint64() * 5,
+ }
+}
+
+var testHeader = makeTestHeader(big.NewInt(10000))
+
+var vmBlockCtx = BlockContext{
+ CanTransfer: func(db StateDB, addr common.Address, amount *uint256.Int) bool {
+ // Assume all addresses but the zero address can transfer for testing
+ if addr == common.ZeroAddress {
+ return false
+ } else {
+ return true
+ }
+ },
+ Transfer: func(db StateDB, a1, a2 common.Address, i *uint256.Int) {
+ panic("transfer: not implemented")
+ },
+ GetHash: func(u uint64) common.Hash {
+ panic("getHash: not implemented")
+ },
+ Coinbase: common.Address{},
+ BlockNumber: new(big.Int).Set(testHeader.Number),
+ Time: testHeader.Time,
+}
+
+var vmTxCtx = TxContext{
+ GasPrice: common.Big1,
+ Origin: common.HexToAddress("a11ce"),
+}
+
+// Create a global mock EVM for use in the following tests.
+var mockEVM = &EVM{
+ chainConfig: params.TestChainConfig,
+ Context: vmBlockCtx,
+ TxContext: vmTxCtx,
+}
+
+var mockPrecompileContext = NewContext(common.HexToAddress("1337"), mockEVM)
+
+func TestPrecompileTransfer(t *testing.T) {
+ type args struct {
+ input []byte
+ ctx *celoPrecompileContext
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expectedErr string
+ }{
+ {
+ name: "Test transfer with invalid caller",
+ args: args{
+ input: []byte(""),
+ ctx: mockPrecompileContext,
+ },
+ wantErr: true,
+ expectedErr: "unable to call transfer from unpermissioned address",
+ }, {
+ name: "Test transfer with short input",
+ args: args{
+ input: []byte("0000"),
+ ctx: NewContext(addresses.MainnetAddresses.CeloToken, mockEVM),
+ },
+ wantErr: true,
+ expectedErr: "invalid input length",
+ }, {
+ name: "Test transferring from zero address (was used for minting on Celo L1)",
+ args: args{
+ // input consists of (from, to, value), each 32 bytes. In this case (0x0, 0x1, 2).
+ input: []byte{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ },
+ ctx: NewContext(addresses.MainnetAddresses.CeloToken, mockEVM),
+ },
+ wantErr: true,
+ expectedErr: "insufficient balance for transfer",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &transfer{}
+ _, err := c.Run(tt.args.input, tt.args.ctx)
+ if tt.wantErr {
+ if err == nil {
+ t.Errorf("transfer.Run() expected error = %v", tt.expectedErr)
+ } else if err.Error() != tt.expectedErr {
+ t.Errorf("transfer.Run() error = %v, expected %v", err, tt.wantErr)
+ }
+ }
+ })
+ }
+}
diff --git op-geth/core/vm/contracts.go Celo/core/vm/contracts.go
index 58146bdc526fa331be3fe349992fe27431fb07fc..4a3190fad17f0c072cc5aa7b973412bcd5971804 100644
--- op-geth/core/vm/contracts.go
+++ Celo/core/vm/contracts.go
@@ -176,8 +176,21 @@ common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
}
+// PrecompiledContractsCel2 contains the default set of pre-compiled Ethereum
+// contracts used in the Cel2 release which don't require the extra
+// celoPrecompileContext, while PrecompiledCeloContractsCel2 contains those
+// that do.
+var PrecompiledContractsCel2 = PrecompiledContractsGranite
+
+// PrecompiledCeloContractsCel2 contains a set of pre-compiled contracts used
+// in the Cel2 release which require the extra celoPrecompileContext.
+var PrecompiledCeloContractsCel2 = map[common.Address]CeloPrecompiledContract{
+ celoPrecompileAddress(2): &transfer{},
+}
+
var (
PrecompiledAddressesGranite []common.Address
+ PrecompiledAddressesCel2 []common.Address
PrecompiledAddressesFjord []common.Address
PrecompiledAddressesPrague []common.Address
PrecompiledAddressesCancun []common.Address
@@ -242,8 +255,9 @@ func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
return maps.Clone(activePrecompiledContracts(rules))
}
-// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
-func ActivePrecompiles(rules params.Rules) []common.Address {
+// OptimismPrecompiles returns the original Optimism precompiles enabled with
+// the current configuration.
+func OptimismPrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsOptimismGranite:
return PrecompiledAddressesGranite
@@ -264,12 +278,30 @@ return PrecompiledAddressesHomestead
}
}
+// ActivePrecompiles returns the precompiles enabled with the current configuration.
+func ActivePrecompiles(rules params.Rules) []common.Address {
+ addresses := OptimismPrecompiles(rules)
+
+ if !rules.IsCel2 {
+ return addresses
+ }
+
+ PrecompiledAddressesCel2 = PrecompiledAddressesCel2[:0]
+ PrecompiledAddressesCel2 = append(PrecompiledAddressesCel2, addresses...)
+
+ for k := range PrecompiledCeloContractsCel2 {
+ PrecompiledAddressesCel2 = append(PrecompiledAddressesCel2, k)
+ }
+
+ return PrecompiledAddressesCel2
+}
+
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
// It returns
// - the returned bytes,
// - the _remaining_ gas,
// - any error that occurred
-func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) {
+func RunPrecompiledContract(p CeloPrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks, ctx *celoPrecompileContext) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
@@ -278,7 +310,7 @@ if logger != nil && logger.OnGasChange != nil {
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
}
suppliedGas -= gasCost
- output, err := p.Run(input)
+ output, err := p.Run(input, ctx)
return output, suppliedGas, err
}
diff --git op-geth/core/vm/contracts_fuzz_test.go Celo/core/vm/contracts_fuzz_test.go
index 1e5cc8007471aa701d28c81706a27a45b3f319d7..6e5ce2f6f3615cfc21339348fe12d7e4feaaf91a 100644
--- op-geth/core/vm/contracts_fuzz_test.go
+++ Celo/core/vm/contracts_fuzz_test.go
@@ -36,7 +36,7 @@ if gas > 10_000_000 {
return
}
inWant := string(input)
- RunPrecompiledContract(p, input, gas, nil)
+ RunPrecompiledContract(p, input, gas, nil, mockPrecompileContext)
if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a)
}
diff --git op-geth/core/vm/contracts_test.go Celo/core/vm/contracts_test.go
index 414a867f20888aa01f383baf880b10d7126d3dca..47e0f7ebb358a8f4ee8a3e00fc6bc2d94419ca45 100644
--- op-geth/core/vm/contracts_test.go
+++ Celo/core/vm/contracts_test.go
@@ -46,30 +46,30 @@ }
// allPrecompiles does not map to the actual set of precompiles, as it also contains
// repriced versions of precompiles at certain slots
-var allPrecompiles = map[common.Address]PrecompiledContract{
- common.BytesToAddress([]byte{1}): &ecrecover{},
- common.BytesToAddress([]byte{2}): &sha256hash{},
- common.BytesToAddress([]byte{3}): &ripemd160hash{},
- common.BytesToAddress([]byte{4}): &dataCopy{},
- common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
- common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
- common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
- common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
- common.BytesToAddress([]byte{8}): &bn256PairingGranite{},
- common.BytesToAddress([]byte{9}): &blake2F{},
- common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
+var allPrecompiles = map[common.Address]CeloPrecompiledContract{
+ common.BytesToAddress([]byte{1}): &wrap{&ecrecover{}},
+ common.BytesToAddress([]byte{2}): &wrap{&sha256hash{}},
+ common.BytesToAddress([]byte{3}): &wrap{&ripemd160hash{}},
+ common.BytesToAddress([]byte{4}): &wrap{&dataCopy{}},
+ common.BytesToAddress([]byte{5}): &wrap{&bigModExp{eip2565: false}},
+ common.BytesToAddress([]byte{0xf5}): &wrap{&bigModExp{eip2565: true}},
+ common.BytesToAddress([]byte{6}): &wrap{&bn256AddIstanbul{}},
+ common.BytesToAddress([]byte{7}): &wrap{&bn256ScalarMulIstanbul{}},
+ common.BytesToAddress([]byte{8}): &wrap{&bn256PairingGranite{}},
+ common.BytesToAddress([]byte{9}): &wrap{&blake2F{}},
+ common.BytesToAddress([]byte{0x0a}): &wrap{&kzgPointEvaluation{}},
- common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
+ common.BytesToAddress([]byte{0x01, 0x00}): &wrap{&p256Verify{}},
- common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{},
- common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{},
- common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{},
- common.BytesToAddress([]byte{0x0f, 0x0d}): &bls12381G2Add{},
- common.BytesToAddress([]byte{0x0f, 0x0e}): &bls12381G2Mul{},
- common.BytesToAddress([]byte{0x0f, 0x0f}): &bls12381G2MultiExp{},
- common.BytesToAddress([]byte{0x0f, 0x10}): &bls12381Pairing{},
- common.BytesToAddress([]byte{0x0f, 0x11}): &bls12381MapG1{},
- common.BytesToAddress([]byte{0x0f, 0x12}): &bls12381MapG2{},
+ common.BytesToAddress([]byte{0x0f, 0x0a}): &wrap{&bls12381G1Add{}},
+ common.BytesToAddress([]byte{0x0f, 0x0b}): &wrap{&bls12381G1Mul{}},
+ common.BytesToAddress([]byte{0x0f, 0x0c}): &wrap{&bls12381G1MultiExp{}},
+ common.BytesToAddress([]byte{0x0f, 0x0d}): &wrap{&bls12381G2Add{}},
+ common.BytesToAddress([]byte{0x0f, 0x0e}): &wrap{&bls12381G2Mul{}},
+ common.BytesToAddress([]byte{0x0f, 0x0f}): &wrap{&bls12381G2MultiExp{}},
+ common.BytesToAddress([]byte{0x0f, 0x10}): &wrap{&bls12381Pairing{}},
+ common.BytesToAddress([]byte{0x0f, 0x11}): &wrap{&bls12381MapG1{}},
+ common.BytesToAddress([]byte{0x0f, 0x12}): &wrap{&bls12381MapG2{}},
}
// EIP-152 test vectors
@@ -101,7 +101,7 @@ p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
- if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil {
+ if res, _, err := RunPrecompiledContract(p, in, gas, nil, mockPrecompileContext); err != nil {
t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@@ -123,7 +123,7 @@ in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
- _, _, err := RunPrecompiledContract(p, in, gas, nil)
+ _, _, err := RunPrecompiledContract(p, in, gas, nil, mockPrecompileContext)
if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err)
}
@@ -140,7 +140,7 @@ p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) {
- _, _, err := RunPrecompiledContract(p, in, gas, nil)
+ _, _, err := RunPrecompiledContract(p, in, gas, nil, mockPrecompileContext)
if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
}
@@ -172,7 +172,7 @@ start := time.Now()
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
copy(data, in)
- res, _, err = RunPrecompiledContract(p, data, reqGas, nil)
+ res, _, err = RunPrecompiledContract(p, data, reqGas, nil, mockPrecompileContext)
}
bench.StopTimer()
elapsed := uint64(time.Since(start))
diff --git op-geth/core/vm/evm.go Celo/core/vm/evm.go
index 1945a3b4dbb68f2a5a5c060f0031fd3778c232dd..f35dd5a2e0d4fa02e2a0ba056c114e1f8b55f076 100644
--- op-geth/core/vm/evm.go
+++ Celo/core/vm/evm.go
@@ -40,13 +40,27 @@ // and is used by the BLOCKHASH EVM op code.
GetHashFunc func(uint64) common.Hash
)
-func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
+func (evm *EVM) precompile(addr common.Address) (CeloPrecompiledContract, bool) {
p, ok := evm.precompiles[addr]
if evm.Config.PrecompileOverrides != nil {
override := evm.Config.PrecompileOverrides(evm.chainRules, p, addr)
- return override, override != nil
+ p = override
+ ok = override != nil
+ }
+
+ var cp CeloPrecompiledContract
+ if ok {
+ cp = &wrap{p}
+ } else {
+ var celoPrecompiles map[common.Address]CeloPrecompiledContract
+ switch {
+ case evm.chainRules.IsCel2:
+ celoPrecompiles = PrecompiledCeloContractsCel2
+ }
+ cp, ok = celoPrecompiles[addr]
}
- return p, ok
+
+ return cp, ok
}
// BlockContext provides the EVM with auxiliary information. Once provided
@@ -71,6 +85,9 @@ Difficulty *big.Int // Provides information for DIFFICULTY
BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price)
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
Random *common.Hash // Provides information for PREVRANDAO
+
+ // Celo specific information
+ FeeCurrencyContext common.FeeCurrencyContext
}
// TxContext provides the EVM with information about a transaction.
@@ -222,7 +239,7 @@ }
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
if isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
+ ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, NewContext(caller.Address(), evm))
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
@@ -292,7 +309,7 @@ var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
+ ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, NewContext(caller.Address(), evm))
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
@@ -344,7 +361,7 @@ var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
+ ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, NewContext(caller.Address(), evm))
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
@@ -399,7 +416,7 @@ // future scenarios
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
if p, isPrecompile := evm.precompile(addr); isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
+ ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, NewContext(caller.Address(), evm))
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
Celo fee currencies
+2072
-240
Fee currencies allow to pay transaction fees with ERC20 tokens.
diff --git op-geth/contracts/fee_currencies.go Celo/contracts/fee_currencies.go
new file mode 100644
index 0000000000000000000000000000000000000000..d4c99ed0efe942e66deb279a65e4f6f7376fc636
--- /dev/null
+++ Celo/contracts/fee_currencies.go
@@ -0,0 +1,299 @@
+package contracts
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/contracts/addresses"
+ "github.com/ethereum/go-ethereum/contracts/celo/abigen"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+var feeCurrencyABI *abi.ABI
+
+var ErrFeeCurrencyEVMCall = errors.New("fee-currency contract error during internal EVM call")
+
+func init() {
+ var err error
+ feeCurrencyABI, err = abigen.FeeCurrencyMetaData.GetAbi()
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Returns nil if debit is possible, used in tx pool validation
+func TryDebitFees(tx *types.Transaction, from common.Address, backend *CeloBackend, feeContext common.FeeCurrencyContext) error {
+ amount := new(big.Int).SetUint64(tx.Gas())
+ amount.Mul(amount, tx.GasFeeCap())
+
+ snapshot := backend.State.Snapshot()
+ evm := backend.NewEVM(&feeContext)
+ _, err := DebitFees(evm, tx.FeeCurrency(), from, amount)
+ backend.State.RevertToSnapshot(snapshot)
+ return err
+}
+
+// Debits transaction fees from the transaction sender and stores them in the temporary address
+func DebitFees(evm *vm.EVM, feeCurrency *common.Address, address common.Address, amount *big.Int) (uint64, error) {
+ // Hide this function from traces
+ if evm.Config.Tracer != nil && !evm.Config.Tracer.TraceDebitCredit {
+ origTracer := evm.Config.Tracer
+ defer func() {
+ evm.Config.Tracer = origTracer
+ }()
+ evm.Config.Tracer = nil
+ }
+
+ if amount.Cmp(big.NewInt(0)) == 0 {
+ return 0, nil
+ }
+
+ maxIntrinsicGasCost, ok := common.MaxAllowedIntrinsicGasCost(evm.Context.FeeCurrencyContext.IntrinsicGasCosts, feeCurrency)
+ if !ok {
+ return 0, fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency)
+ }
+
+ leftoverGas, err := evm.CallWithABI(
+ feeCurrencyABI, "debitGasFees", *feeCurrency, maxIntrinsicGasCost,
+ // debitGasFees(address from, uint256 value) parameters
+ address, amount,
+ )
+ if err != nil {
+ if errors.Is(err, vm.ErrOutOfGas) {
+ // This basically is a configuration / contract error, since
+ // the contract itself used way more gas than was expected (including grace limit)
+ return 0, fmt.Errorf(
+ "%w: surpassed maximum allowed intrinsic gas for DebitFees() in fee-currency: %w",
+ ErrFeeCurrencyEVMCall,
+ err,
+ )
+ }
+ return 0, fmt.Errorf(
+ "%w: DebitFees() call error: %w",
+ ErrFeeCurrencyEVMCall,
+ err,
+ )
+ }
+
+ gasUsed := maxIntrinsicGasCost - leftoverGas
+ log.Trace("DebitFees called", "feeCurrency", *feeCurrency, "gasUsed", gasUsed)
+ return gasUsed, err
+}
+
+// Credits fees to the respective parties
+// - the base fee goes to the fee handler
+// - the transaction tip goes to the miner
+// - the l1 data fee goes the the data fee receiver, is the node runs in rollup mode
+// - remaining funds are refunded to the transaction sender
+func CreditFees(
+ evm *vm.EVM,
+ feeCurrency *common.Address,
+ txSender, tipReceiver, baseFeeReceiver, l1DataFeeReceiver common.Address,
+ refund, feeTip, baseFee, l1DataFee *big.Int,
+ gasUsedDebit uint64,
+) error {
+ // Hide this function from traces
+ if evm.Config.Tracer != nil && !evm.Config.Tracer.TraceDebitCredit {
+ origTracer := evm.Config.Tracer
+ defer func() {
+ evm.Config.Tracer = origTracer
+ }()
+ evm.Config.Tracer = nil
+ }
+
+ // Our old `creditGasFees` function does not accept an l1DataFee and
+ // the fee currencies do not implement the new interface yet. Since tip
+ // and data fee both go to the sequencer, we can work around that for
+ // now by addint the l1DataFee to the tip.
+ if l1DataFee != nil {
+ feeTip = new(big.Int).Add(feeTip, l1DataFee)
+ }
+
+ // Not all fee currencies can handle a receiver being the zero address.
+ // In that case send the fee to the base fee recipient, which we know is non-zero.
+ if tipReceiver.Cmp(common.ZeroAddress) == 0 {
+ tipReceiver = baseFeeReceiver
+ }
+ maxAllowedGasForDebitAndCredit, ok := common.MaxAllowedIntrinsicGasCost(evm.Context.FeeCurrencyContext.IntrinsicGasCosts, feeCurrency)
+ if !ok {
+ return fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency)
+ }
+
+ maxAllowedGasForCredit := maxAllowedGasForDebitAndCredit - gasUsedDebit
+ leftoverGas, err := evm.CallWithABI(
+ feeCurrencyABI, "creditGasFees", *feeCurrency, maxAllowedGasForCredit,
+ // function creditGasFees(
+ // address from,
+ // address feeRecipient,
+ // address, // gatewayFeeRecipient, unused
+ // address communityFund,
+ // uint256 refund,
+ // uint256 tipTxFee,
+ // uint256, // gatewayFee, unused
+ // uint256 baseTxFee
+ // )
+ txSender, tipReceiver, common.ZeroAddress, baseFeeReceiver, refund, feeTip, common.Big0, baseFee,
+ )
+ if err != nil {
+ if errors.Is(err, vm.ErrOutOfGas) {
+ // This is a configuration / contract error, since
+ // the contract itself used way more gas than was expected (including grace limit)
+ return fmt.Errorf(
+ "%w: surpassed maximum allowed intrinsic gas for CreditFees() in fee-currency: %w",
+ ErrFeeCurrencyEVMCall,
+ err,
+ )
+ }
+ return fmt.Errorf(
+ "%w: CreditFees() call error: %w",
+ ErrFeeCurrencyEVMCall,
+ err,
+ )
+ }
+
+ gasUsed := maxAllowedGasForCredit - leftoverGas
+ log.Trace("CreditFees called", "feeCurrency", *feeCurrency, "gasUsed", gasUsed)
+
+ intrinsicGas, ok := common.CurrencyIntrinsicGasCost(evm.Context.FeeCurrencyContext.IntrinsicGasCosts, feeCurrency)
+ if !ok {
+ // this will never happen
+ return fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency)
+ }
+ gasUsedForDebitAndCredit := gasUsedDebit + gasUsed
+ if gasUsedForDebitAndCredit > intrinsicGas {
+ log.Info(
+ "Gas usage for debit+credit exceeds intrinsic gas!",
+ "gasUsed", gasUsedForDebitAndCredit,
+ "intrinsicGas", intrinsicGas,
+ "feeCurrency", feeCurrency,
+ )
+ }
+ return err
+}
+
+func GetRegisteredCurrencies(caller *abigen.FeeCurrencyDirectoryCaller) ([]common.Address, error) {
+ currencies, err := caller.GetCurrencies(&bind.CallOpts{})
+ if err != nil {
+ return currencies, fmt.Errorf("failed to get registered tokens: %w", err)
+ }
+ return currencies, nil
+}
+
+// GetExchangeRates returns the exchange rates for the provided gas currencies
+func GetExchangeRates(caller *CeloBackend) (common.ExchangeRates, error) {
+ directory, err := abigen.NewFeeCurrencyDirectoryCaller(
+ addresses.GetAddressesOrDefault(caller.ChainConfig.ChainID, addresses.MainnetAddresses).FeeCurrencyDirectory,
+ caller,
+ )
+ if err != nil {
+ return common.ExchangeRates{}, fmt.Errorf("failed to access FeeCurrencyDirectory: %w", err)
+ }
+ currencies, err := GetRegisteredCurrencies(directory)
+ if err != nil {
+ return common.ExchangeRates{}, err
+ }
+ return getExchangeRatesForTokens(directory, currencies)
+}
+
+// GetFeeCurrencyContext returns the fee currency block context for all registered gas currencies from CELO
+func GetFeeCurrencyContext(caller *CeloBackend) (common.FeeCurrencyContext, error) {
+ var feeContext common.FeeCurrencyContext
+ directory, err := abigen.NewFeeCurrencyDirectoryCaller(
+ addresses.GetAddressesOrDefault(caller.ChainConfig.ChainID, addresses.MainnetAddresses).FeeCurrencyDirectory,
+ caller,
+ )
+ if err != nil {
+ return feeContext, fmt.Errorf("failed to access FeeCurrencyDirectory: %w", err)
+ }
+
+ currencies, err := GetRegisteredCurrencies(directory)
+ if err != nil {
+ return feeContext, err
+ }
+ rates, err := getExchangeRatesForTokens(directory, currencies)
+ if err != nil {
+ return feeContext, err
+ }
+ intrinsicGas, err := getIntrinsicGasForTokens(directory, currencies)
+ if err != nil {
+ return feeContext, err
+ }
+ return common.FeeCurrencyContext{
+ ExchangeRates: rates,
+ IntrinsicGasCosts: intrinsicGas,
+ }, nil
+}
+
+// GetBalanceERC20 returns an account's balance on a given ERC20 currency
+func GetBalanceERC20(caller bind.ContractCaller, accountOwner common.Address, contractAddress common.Address) (result *big.Int, err error) {
+ token, err := abigen.NewFeeCurrencyCaller(contractAddress, caller)
+ if err != nil {
+ return nil, fmt.Errorf("failed to access FeeCurrency: %w", err)
+ }
+
+ balance, err := token.BalanceOf(&bind.CallOpts{}, accountOwner)
+ if err != nil {
+ return nil, err
+ }
+
+ return balance, nil
+}
+
+// GetFeeBalance returns the account's balance from the specified feeCurrency
+// (if feeCurrency is nil or ZeroAddress, native currency balance is returned).
+func GetFeeBalance(backend *CeloBackend, account common.Address, feeCurrency *common.Address) *big.Int {
+ if feeCurrency == nil || *feeCurrency == common.ZeroAddress {
+ return backend.State.GetBalance(account).ToBig()
+ }
+ balance, err := GetBalanceERC20(backend, account, *feeCurrency)
+ if err != nil {
+ log.Error("Error while trying to get ERC20 balance:", "cause", err, "contract", feeCurrency.Hex(), "account", account.Hex())
+ }
+ return balance
+}
+
+// getIntrinsicGasForTokens returns the intrinsic gas costs for the provided gas currencies from CELO
+func getIntrinsicGasForTokens(caller *abigen.FeeCurrencyDirectoryCaller, tokens []common.Address) (common.IntrinsicGasCosts, error) {
+ gasCosts := common.IntrinsicGasCosts{}
+ for _, tokenAddress := range tokens {
+ config, err := caller.GetCurrencyConfig(&bind.CallOpts{}, tokenAddress)
+ if err != nil {
+ log.Error("Failed to get intrinsic gas cost for gas currency!", "err", err, "tokenAddress", tokenAddress.Hex())
+ continue
+ }
+ if !config.IntrinsicGas.IsUint64() {
+ log.Error("Intrinsic gas cost exceeds MaxUint64 limit, capping at MaxUint64", "err", err, "tokenAddress", tokenAddress.Hex())
+ gasCosts[tokenAddress] = math.MaxUint64
+ } else {
+ gasCosts[tokenAddress] = config.IntrinsicGas.Uint64()
+ }
+ }
+ return gasCosts, nil
+}
+
+// getExchangeRatesForTokens returns the exchange rates for the provided gas currencies from CELO
+func getExchangeRatesForTokens(caller *abigen.FeeCurrencyDirectoryCaller, tokens []common.Address) (common.ExchangeRates, error) {
+ exchangeRates := common.ExchangeRates{}
+ for _, tokenAddress := range tokens {
+ rate, err := caller.GetExchangeRate(&bind.CallOpts{}, tokenAddress)
+ if err != nil {
+ log.Error("Failed to get medianRate for gas currency!", "err", err, "tokenAddress", tokenAddress.Hex())
+ continue
+ }
+ if rate.Numerator.Sign() <= 0 || rate.Denominator.Sign() <= 0 {
+ log.Error("Bad exchange rate for fee currency", "tokenAddress", tokenAddress.Hex(), "numerator", rate.Numerator, "denominator", rate.Denominator)
+ continue
+ }
+ exchangeRates[tokenAddress] = new(big.Rat).SetFrac(rate.Numerator, rate.Denominator)
+ }
+
+ return exchangeRates, nil
+}
diff --git op-geth/core/blockchain_celo_test.go Celo/core/blockchain_celo_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0f2a4bb1d0bf1ad3a0640d83d86bdb4f2570b01
--- /dev/null
+++ Celo/core/blockchain_celo_test.go
@@ -0,0 +1,146 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/contracts/addresses"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/assert"
+)
+
+// TestNativeTransferWithFeeCurrency tests the following:
+//
+// 1. A transaction whose gasFeeCap is greater than the baseFee is valid.
+// 2. Gas accounting for celo fee currency transactions is correct.
+// 3. Only the transaction's tip will be received by the coinbase.
+// 4. The transaction sender pays for both the tip and baseFee.
+// 5. The base fee goes to the fee handler.
+func TestNativeTransferWithFeeCurrency(t *testing.T) {
+ testNativeTransferWithFeeCurrency(t, rawdb.HashScheme, DevFeeCurrencyAddr2)
+ testNativeTransferWithFeeCurrency(t, rawdb.PathScheme, DevFeeCurrencyAddr2)
+}
+
+// Test that the gas price is checked against the base fee in the same currency.
+// The tx has a GasFeeCap that matches the blocks base fee, so it would succeed
+// when compared without currency conversion, but it must fail if the check is
+// correct.
+func TestNativeTransferWithFeeCurrencyAndTooLowGasPrice(t *testing.T) {
+ assert.PanicsWithError(t, "max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 875000000, baseFee: 1750000000",
+ func() { testNativeTransferWithFeeCurrency(t, rawdb.HashScheme, DevFeeCurrencyAddr) },
+ )
+}
+
+func testNativeTransferWithFeeCurrency(t *testing.T, scheme string, feeCurrencyAddr common.Address) {
+ var (
+ aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
+ engine = ethash.NewFaker()
+
+ // A sender who makes transactions, has some funds
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ config = *params.AllEthashProtocolChanges
+ funds = DevBalance
+ gspec = &Genesis{
+ Config: &config,
+ Alloc: CeloGenesisAccounts(addr1),
+ }
+ )
+ gspec.Config.Cel2Time = uint64ptr(0)
+
+ signer := types.LatestSigner(gspec.Config)
+
+ _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
+ b.SetCoinbase(common.Address{1})
+
+ txdata := &types.CeloDynamicFeeTxV2{
+ ChainID: gspec.Config.ChainID,
+ Nonce: 0,
+ To: &aa,
+ Gas: 100000,
+ GasFeeCap: b.header.BaseFee,
+ GasTipCap: big.NewInt(2),
+ Data: []byte{},
+ FeeCurrency: &feeCurrencyAddr,
+ }
+ tx := types.NewTx(txdata)
+ tx, _ = types.SignTx(tx, signer, key1)
+
+ b.AddTx(tx)
+ })
+ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
+ if err != nil {
+ t.Fatalf("failed to create tester chain: %v", err)
+ }
+ defer chain.Stop()
+
+ if n, err := chain.InsertChain(blocks); err != nil {
+ t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+ }
+
+ block := chain.GetBlockByNumber(1)
+
+ // 1+2: Ensure correct gas amount is deducted
+ expectedGas := uint64(71000)
+ if block.GasUsed() != expectedGas {
+ t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed())
+ }
+
+ state, _ := chain.State()
+
+ backend := contracts.CeloBackend{
+ ChainConfig: chain.chainConfig,
+ State: state,
+ }
+ exchangeRates, err := contracts.GetExchangeRates(&backend)
+ if err != nil {
+ t.Fatal("could not get exchange rates")
+ }
+ baseFeeInFeeCurrency, _ := exchange.ConvertCeloToCurrency(exchangeRates, &feeCurrencyAddr, block.BaseFee())
+ actual, _ := contracts.GetBalanceERC20(&backend, block.Coinbase(), feeCurrencyAddr)
+
+ // 3: Ensure that miner received only the tx's tip.
+ expected := new(big.Int).SetUint64(block.GasUsed() * block.Transactions()[0].GasTipCap().Uint64())
+ if actual.Cmp(expected) != 0 {
+ t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual)
+ }
+
+ // 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee).
+ actual, _ = contracts.GetBalanceERC20(&backend, addr1, feeCurrencyAddr)
+ actual = new(big.Int).Sub(funds, actual)
+ expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].GasTipCap().Uint64() + baseFeeInFeeCurrency.Uint64()))
+ if actual.Cmp(expected) != 0 {
+ t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
+ }
+
+ // 5: Check that base fee has been moved to the fee handler.
+ actual, _ = contracts.GetBalanceERC20(&backend, addresses.MainnetAddresses.FeeHandler, feeCurrencyAddr)
+ expected = new(big.Int).SetUint64(block.GasUsed() * baseFeeInFeeCurrency.Uint64())
+ if actual.Cmp(expected) != 0 {
+ t.Fatalf("fee handler balance incorrect: expected %d, got %d", expected, actual)
+ }
+}
diff --git op-geth/core/celo_evm.go Celo/core/celo_evm.go
new file mode 100644
index 0000000000000000000000000000000000000000..3858f30aeee2f9891893b17c1f90619334b8beb9
--- /dev/null
+++ Celo/core/celo_evm.go
@@ -0,0 +1,38 @@
+package core
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+func GetFeeCurrencyContext(header *types.Header, config *params.ChainConfig, statedb vm.StateDB) *common.FeeCurrencyContext {
+ if !config.IsCel2(header.Time) {
+ return &common.FeeCurrencyContext{}
+ }
+
+ caller := &contracts.CeloBackend{ChainConfig: config, State: statedb}
+
+ feeCurrencyContext, err := contracts.GetFeeCurrencyContext(caller)
+ if err != nil {
+ log.Error("Error fetching exchange rates!", "err", err)
+ }
+ return &feeCurrencyContext
+}
+
+func GetExchangeRates(header *types.Header, config *params.ChainConfig, statedb vm.StateDB) common.ExchangeRates {
+ if !config.IsCel2(header.Time) {
+ return nil
+ }
+
+ caller := &contracts.CeloBackend{ChainConfig: config, State: statedb}
+
+ feeCurrencyContext, err := contracts.GetFeeCurrencyContext(caller)
+ if err != nil {
+ log.Error("Error fetching exchange rates!", "err", err)
+ }
+ return feeCurrencyContext.ExchangeRates
+}
diff --git op-geth/core/evm.go Celo/core/evm.go
index e8016fbb782145ac28fdf78e02e59f0e2b9784be..11a665b4f7be978ef83f19f4a688f861c044d29f 100644
--- op-geth/core/evm.go
+++ Celo/core/evm.go
@@ -40,7 +40,7 @@ GetHeader(common.Hash, uint64) *types.Header
}
// NewEVMBlockContext creates a new context for use in the EVM.
-func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, config *params.ChainConfig, statedb types.StateGetter) vm.BlockContext {
+func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, config *params.ChainConfig, statedb vm.StateDB, feeCurrencyContext *common.FeeCurrencyContext) vm.BlockContext {
var (
beneficiary common.Address
baseFee *big.Int
@@ -63,7 +63,7 @@ }
if header.Difficulty.Sign() == 0 {
random = &header.MixDigest
}
- return vm.BlockContext{
+ blockContext := vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
@@ -77,6 +77,12 @@ GasLimit: header.GasLimit,
Random: random,
L1CostFunc: types.NewL1CostFunc(config, statedb),
}
+
+ if config.IsCel2(header.Time) {
+ blockContext.FeeCurrencyContext = *feeCurrencyContext
+ }
+
+ return blockContext
}
// NewEVMTxContext creates a new transaction context for a single transaction.
diff --git op-geth/core/state_processor.go Celo/core/state_processor.go
index a1042cf868eac6a0e09ae481547ee435c3e84ead..363cd3ee40381c6d84b9e4b87dbe740ee037e542 100644
--- op-geth/core/state_processor.go
+++ Celo/core/state_processor.go
@@ -21,11 +21,13 @@ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
@@ -74,7 +76,8 @@ context vm.BlockContext
signer = types.MakeSigner(p.config, header.Number, header.Time)
err error
)
- context = NewEVMBlockContext(header, p.chain, nil, p.config, statedb)
+ feeCurrencyContext := GetFeeCurrencyContext(header, p.config, statedb)
+ context = NewEVMBlockContext(header, p.chain, nil, p.config, statedb, feeCurrencyContext)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
@@ -84,7 +87,7 @@ ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
}
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
- msg, err := TransactionToMessage(tx, signer, header.BaseFee)
+ msg, err := TransactionToMessage(tx, signer, header.BaseFee, context.FeeCurrencyContext.ExchangeRates)
if err != nil {
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
@@ -180,6 +183,17 @@ receipt.DepositReceiptVersion = new(uint64)
*receipt.DepositReceiptVersion = types.CanyonDepositReceiptVersion
}
}
+ if tx.Type() == types.CeloDynamicFeeTxV2Type {
+ alternativeBaseFee := evm.Context.BaseFee
+ if tx.FeeCurrency() != nil {
+ var err error
+ alternativeBaseFee, err = exchange.ConvertCeloToCurrency(evm.Context.FeeCurrencyContext.ExchangeRates, tx.FeeCurrency(), evm.Context.BaseFee)
+ if err != nil {
+ log.Error("Can't calc base fee in currency for receipt", "err", err)
+ }
+ }
+ receipt.BaseFee = new(big.Int).Set(alternativeBaseFee)
+ }
if tx.Type() == types.BlobTxType {
receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob)
receipt.BlobGasPrice = evm.Context.BlobBaseFee
@@ -209,16 +223,16 @@ // ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
-func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
- return ApplyTransactionExtended(config, bc, author, gp, statedb, header, tx, usedGas, cfg, nil)
+func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, feeCurrencyContext *common.FeeCurrencyContext) (*types.Receipt, error) {
+ return ApplyTransactionExtended(config, bc, author, gp, statedb, header, tx, usedGas, cfg, nil, feeCurrencyContext)
}
type ApplyTransactionOpts struct {
PostValidation func(evm *vm.EVM, result *ExecutionResult) error
}
-func ApplyTransactionExtended(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, extraOpts *ApplyTransactionOpts) (*types.Receipt, error) {
- msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
+func ApplyTransactionExtended(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, extraOpts *ApplyTransactionOpts, feeCurrencyContext *common.FeeCurrencyContext) (*types.Receipt, error) {
+ msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee, feeCurrencyContext.ExchangeRates)
if err != nil {
return nil, err
}
@@ -226,7 +240,7 @@ if extraOpts != nil {
msg.PostValidation = extraOpts.PostValidation
}
// Create a new context to be used in the EVM environment
- blockContext := NewEVMBlockContext(header, bc, author, config, statedb)
+ blockContext := NewEVMBlockContext(header, bc, author, config, statedb, feeCurrencyContext)
txContext := NewEVMTxContext(msg)
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
diff --git op-geth/core/state_transition.go Celo/core/state_transition.go
index 2de1cad7626c317894ed184a19daecc4f5e44c4b..edd0bd5eae19a835eb3135712d267289bbe89cb1 100644
--- op-geth/core/state_transition.go
+++ Celo/core/state_transition.go
@@ -22,11 +22,13 @@ "math"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
+ "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@@ -68,7 +70,7 @@ return common.CopyBytes(result.ReturnData)
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
-func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool, feeCurrency *common.Address, feeIntrinsicGas common.IntrinsicGasCosts) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@@ -110,6 +112,32 @@ }
gas += lenWords * params.InitCodeWordGas
}
}
+
+ // This gas is used for charging user for one `transfer` call to deduct their fee in
+ // non-native currency and (up to) three `transfer` calls for fee payouts.
+ // The three transfers cover the following cases:
+ // - Base fee transfer to the fee handler
+ // - Tip fee transfer to the block proposer
+ // - Refund of leftover fees to the transaction sender
+ //
+ // A user might or might not have a gas refund at the end and even if they do the gas refund might
+ // be smaller than maxGasForDebitAndCreditTransactions. We still decide to deduct and do the refund
+ // since it makes the mining fee more consistent with respect to the gas fee. Otherwise, we would
+ // have to expect the user to estimate the mining fee right or else end up losing
+ // `min(gas sent - gas charged, maxGasForDebitAndCreditTransactions)` extra.
+ // In this case, however, the user always ends up paying `maxGasForDebitAndCreditTransactions`
+ // keeping it consistent.
+ if feeCurrency != nil {
+ intrinsicGasForFeeCurrency, ok := common.CurrencyIntrinsicGasCost(feeIntrinsicGas, feeCurrency)
+ if !ok {
+ return 0, fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency)
+ }
+ if (math.MaxUint64 - gas) < intrinsicGasForFeeCurrency {
+ return 0, ErrGasUintOverflow
+ }
+ gas += intrinsicGasForFeeCurrency
+ }
+
if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
@@ -156,10 +184,18 @@ Mint *big.Int // Mint is the amount to mint before EVM processing, or nil if there is no minting.
RollupCostData types.RollupCostData // RollupCostData caches data to compute the fee we charge for data availability
PostValidation func(evm *vm.EVM, result *ExecutionResult) error
+
+ // Celo additions
+
+ // FeeCurrency specifies the currency for gas fees.
+ // `nil` corresponds to CELO (native currency).
+ // All other values should correspond to ERC20 contract addresses.
+ FeeCurrency *common.Address
+ MaxFeeInFeeCurrency *big.Int // MaxFeeInFeeCurrency is the maximum fee that can be charged in the fee currency.
}
// TransactionToMessage converts a transaction into a Message.
-func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
+func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int, exchangeRates map[common.Address]*big.Rat) (*Message, error) {
msg := &Message{
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
@@ -179,9 +215,19 @@ IsSystemTx: tx.IsSystemTx(),
IsDepositTx: tx.IsDepositTx(),
Mint: tx.Mint(),
RollupCostData: tx.RollupCostData(),
+
+ FeeCurrency: tx.FeeCurrency(),
+ MaxFeeInFeeCurrency: nil, // Will only be set once CIP-66 is implemented
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
+ if tx.Type() == types.CeloDynamicFeeTxV2Type {
+ var err error
+ baseFee, err = exchange.ConvertCeloToCurrency(exchangeRates, msg.FeeCurrency, baseFee)
+ if err != nil {
+ return nil, err
+ }
+ }
msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap)
}
var err error
@@ -189,6 +235,13 @@ msg.From, err = types.Sender(s, tx)
return msg, err
}
+// IsFeeCurrencyDenominated returns whether the gas-price related
+// fields are denominated in a given fee currency or in the native token.
+// This effectively is only true for CIP-64 transactions.
+func (msg *Message) IsFeeCurrencyDenominated() bool {
+ return msg.FeeCurrency != nil && msg.MaxFeeInFeeCurrency == nil
+}
+
// ApplyMessage computes the new state by applying the given message
// against the old state within the environment.
//
@@ -197,6 +250,7 @@ // the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
+ log.Trace("Applying state transition message", "from", msg.From, "nonce", msg.Nonce, "to", msg.To, "gasPrice", msg.GasPrice, "feeCurrency", msg.FeeCurrency, "gas", msg.GasLimit, "value", msg.Value, "data", msg.Data)
return NewStateTransition(evm, msg, gp).TransitionDb()
}
@@ -229,6 +283,8 @@ gasRemaining uint64
initialGas uint64
state vm.StateDB
evm *vm.EVM
+
+ feeCurrencyGasUsed uint64
}
// NewStateTransition initialises and returns a new state transition object.
@@ -267,7 +323,8 @@ if l1Cost != nil {
balanceCheck.Add(balanceCheck, l1Cost)
}
}
- balanceCheck.Add(balanceCheck, st.msg.Value)
+ // Moved to canPayFee
+ // balanceCheck.Add(balanceCheck, st.msg.Value)
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
@@ -281,12 +338,8 @@ blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
mgval.Add(mgval, blobFee)
}
}
- balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
- if overflow {
- return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
- }
- if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
- return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
+ if err := st.canPayFee(balanceCheck); err != nil {
+ return err
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
@@ -298,9 +351,8 @@ }
st.gasRemaining = st.msg.GasLimit
st.initialGas = st.msg.GasLimit
- mgvalU256, _ := uint256.FromBig(mgval)
- st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
- return nil
+
+ return st.subFees(mgval)
}
func (st *StateTransition) preCheck() error {
@@ -343,6 +395,19 @@ return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
msg.From.Hex(), codeHash)
}
}
+
+ // Verify that fee currency is registered
+ if msg.FeeCurrency != nil {
+ if !st.evm.ChainConfig().IsCel2(st.evm.Context.Time) {
+ return ErrCel2NotEnabled
+ } else {
+ if !common.IsCurrencyAllowed(st.evm.Context.FeeCurrencyContext.ExchangeRates, msg.FeeCurrency) {
+ log.Trace("fee currency not allowed", "fee currency address", msg.FeeCurrency)
+ return fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, msg.FeeCurrency)
+ }
+ }
+ }
+
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
@@ -360,14 +425,20 @@ if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
}
+
// This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation.
- if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
+ baseFeeInFeeCurrency, err := exchange.ConvertCeloToCurrency(st.evm.Context.FeeCurrencyContext.ExchangeRates, msg.FeeCurrency, st.evm.Context.BaseFee)
+ if err != nil {
+ return fmt.Errorf("preCheck: %w", err)
+ }
+ if msg.GasFeeCap.Cmp(baseFeeInFeeCurrency) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
- msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
+ msg.From.Hex(), msg.GasFeeCap, baseFeeInFeeCurrency)
}
}
}
+
// Check the blob version validity
if msg.BlobHashes != nil {
// The to field of a blob tx type is mandatory, and a `BlobTx` transaction internally
@@ -470,11 +541,6 @@ // 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call
- // Check clauses 1-3, buy gas if everything is correct
- if err := st.preCheck(); err != nil {
- return nil, err
- }
-
var (
msg = st.msg
sender = vm.AccountRef(msg.From)
@@ -482,8 +548,27 @@ rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
contractCreation = msg.To == nil
)
+ // Execute the preparatory steps for state transition which includes:
+ // - prepare accessList(post-berlin)
+ // - reset transient storage(eip 1153)
+ st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
+
+ // Check clauses 1-3, buy gas if everything is correct
+ if err := st.preCheck(); err != nil {
+ return nil, err
+ }
+
// Check clauses 4-5, subtract intrinsic gas if everything is correct
- gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
+ gas, err := IntrinsicGas(
+ msg.Data,
+ msg.AccessList,
+ contractCreation,
+ rules.IsHomestead,
+ rules.IsIstanbul,
+ rules.IsShanghai,
+ msg.FeeCurrency,
+ st.evm.Context.FeeCurrencyContext.IntrinsicGasCosts,
+ )
if err != nil {
return nil, err
}
@@ -520,11 +605,6 @@ // Check whether the init code size has been exceeded.
if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
}
-
- // Execute the preparatory steps for state transition which includes:
- // - prepare accessList(post-berlin)
- // - reset transient storage(eip 1153)
- st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
var (
ret []byte
@@ -573,43 +653,9 @@ Err: vmerr,
ReturnData: ret,
}, nil
}
- effectiveTip := msg.GasPrice
- if rules.IsLondon {
- effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
- }
- effectiveTipU256, _ := uint256.FromBig(effectiveTip)
- if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
- // Skip fee payment when NoBaseFee is set and the fee fields
- // are 0. This avoids a negative effectiveTip being applied to
- // the coinbase when simulating calls.
- } else {
- fee := new(uint256.Int).SetUint64(st.gasUsed())
- fee.Mul(fee, effectiveTipU256)
- st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
-
- // add the coinbase to the witness iff the fee is greater than 0
- if rules.IsEIP4762 && fee.Sign() != 0 {
- st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true)
- }
-
- // Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules)
- // Note optimismConfig will not be nil if rules.IsOptimismBedrock is true
- if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && rules.IsOptimismBedrock && !st.msg.IsDepositTx {
- gasCost := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)
- amtU256, overflow := uint256.FromBig(gasCost)
- if overflow {
- return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost)
- }
- st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
- if l1Cost := st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time); l1Cost != nil {
- amtU256, overflow = uint256.FromBig(l1Cost)
- if overflow {
- return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost)
- }
- st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
- }
- }
+ if err := st.distributeTxFees(); err != nil {
+ return nil, err
}
return &ExecutionResult{
@@ -633,14 +679,7 @@ }
st.gasRemaining += refund
- // Return ETH for remaining gas, exchanged at the original rate.
- remaining := uint256.NewInt(st.gasRemaining)
- remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
- st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
-
- if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
- st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
- }
+ // Gas refund now handled in `distributeTxFees`
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
diff --git op-geth/core/txpool/blobpool/blobpool.go Celo/core/txpool/blobpool/blobpool.go
index 19908d9e8fb61078ff0242728bbc89cac0c2371f..e94a6d69c7225ecd996a7f939bfa59b02354d444 100644
--- op-geth/core/txpool/blobpool/blobpool.go
+++ Celo/core/txpool/blobpool/blobpool.go
@@ -32,6 +32,7 @@
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
+ "github.com/ethereum/go-ethereum/contracts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
@@ -116,7 +117,7 @@ hash: tx.Hash(),
id: id,
size: size,
nonce: tx.Nonce(),
- costCap: uint256.MustFromBig(tx.Cost()),
+ costCap: uint256.MustFromBig(tx.NativeCost()),
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
@@ -315,6 +316,10 @@ discoverFeed event.Feed // Event feed to send out new tx events on pool discovery (reorg excluded)
insertFeed event.Feed // Event feed to send out new tx events on pool inclusion (reorg included)
lock sync.RWMutex // Mutex protecting the pool during reorg handling
+
+ // Celo specific
+ celoBackend *contracts.CeloBackend // For fee currency balances & exchange rate calculation
+ feeCurrencyContext common.FeeCurrencyContext
}
// New creates a new blob transaction pool to gather, sort and filter inbound
@@ -370,6 +375,7 @@ if err != nil {
return err
}
p.head, p.state = head, state
+ p.recreateCeloProperties()
// Index all transactions on disk and delete anything unprocessable
var fails []uint64
@@ -796,6 +802,7 @@ return
}
p.head = newHead
p.state = statedb
+ p.recreateCeloProperties()
// Run the reorg between the old and new head and figure out which accounts
// need to be rechecked and which transactions need to be readded
@@ -1081,13 +1088,13 @@ // rules and adheres to some heuristic limits of the local node (price and size).
func (p *BlobPool) validateTx(tx *types.Transaction) error {
// Ensure the transaction adheres to basic pool filters (type, size, tip) and
// consensus rules
- baseOpts := &txpool.ValidationOptions{
- Config: p.chain.Config(),
- Accept: 1 << types.BlobTxType,
- MaxSize: txMaxSize,
- MinTip: p.gasTip.ToBig(),
+ baseOpts := &txpool.CeloValidationOptions{
+ Config: p.chain.Config(),
+ AcceptSet: txpool.NewAcceptSet(types.BlobTxType),
+ MaxSize: txMaxSize,
+ MinTip: p.gasTip.ToBig(),
}
- if err := txpool.ValidateTransaction(tx, p.head, p.signer, baseOpts); err != nil {
+ if err := txpool.CeloValidateTransaction(tx, p.head, p.signer, baseOpts, p.feeCurrencyContext); err != nil {
return err
}
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
@@ -1107,20 +1114,24 @@ return have, 0
}
return have, maxTxsPerAccount - have
},
- ExistingExpenditure: func(addr common.Address) *big.Int {
+ ExistingExpenditure: func(addr common.Address) (*big.Int, *big.Int) {
if spent := p.spent[addr]; spent != nil {
- return spent.ToBig()
+ return new(big.Int), spent.ToBig()
}
- return new(big.Int)
+ return new(big.Int), new(big.Int)
},
- ExistingCost: func(addr common.Address, nonce uint64) *big.Int {
+ ExistingCost: func(addr common.Address, nonce uint64) (*big.Int, *big.Int) {
next := p.state.GetNonce(addr)
if uint64(len(p.index[addr])) > nonce-next {
- return p.index[addr][int(nonce-next)].costCap.ToBig()
+ return new(big.Int), p.index[addr][int(nonce-next)].costCap.ToBig()
}
- return nil
+ return nil, nil
+ },
+ ExistingBalance: func(addr common.Address, feeCurrency *common.Address) *big.Int {
+ return contracts.GetFeeBalance(p.celoBackend, addr, feeCurrency)
},
}
+
if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts); err != nil {
return err
}
diff --git op-geth/core/txpool/blobpool/celo_blobpool.go Celo/core/txpool/blobpool/celo_blobpool.go
new file mode 100644
index 0000000000000000000000000000000000000000..7b453a3ca44edcc8b4e218ea1c4233f38c3c1ae8
--- /dev/null
+++ Celo/core/txpool/blobpool/celo_blobpool.go
@@ -0,0 +1,18 @@
+package blobpool
+
+import (
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+func (pool *BlobPool) recreateCeloProperties() {
+ pool.celoBackend = &contracts.CeloBackend{
+ ChainConfig: pool.chain.Config(),
+ State: pool.state,
+ }
+ currencyContext, err := contracts.GetFeeCurrencyContext(pool.celoBackend)
+ if err != nil {
+ log.Error("Error trying to get fee currency context in txpool.", "cause", err)
+ }
+ pool.feeCurrencyContext = currencyContext
+}
diff --git op-geth/core/txpool/celo_validation.go Celo/core/txpool/celo_validation.go
new file mode 100644
index 0000000000000000000000000000000000000000..d82b85db744367f495594d90f91dd89c0d9180d2
--- /dev/null
+++ Celo/core/txpool/celo_validation.go
@@ -0,0 +1,102 @@
+package txpool
+
+import (
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+var (
+
+ // ErrGasPriceDoesNotExceedBaseFeeFloor is returned if the gas price specified is
+ // lower than the configured base-fee-floor
+ ErrGasPriceDoesNotExceedBaseFeeFloor = errors.New("gas-price is less than the base-fee-floor")
+ ErrMinimumEffectiveGasTipBelowMinTip = errors.New("effective gas tip at base-fee-floor is below threshold")
+)
+
+// AcceptSet is a set of accepted transaction types for a transaction subpool.
+type AcceptSet = map[uint8]struct{}
+
+// CeloValidationOptions define certain differences between transaction validation
+// across the different pools without having to duplicate those checks.
+// In comparison to the standard ValidationOptions, the Accept field has been
+// changed to allow to test for CeloDynamicFeeTx types.
+type CeloValidationOptions struct {
+ Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules
+
+ AcceptSet AcceptSet // Set of transaction types that should be accepted for the calling pool
+ MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
+ MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
+
+ EffectiveGasCeil uint64 // if non-zero, a gas ceiling to enforce independent of the header's gaslimit value
+}
+
+// NewAcceptSet creates a new AcceptSet with the types provided.
+func NewAcceptSet(types ...uint8) AcceptSet {
+ m := make(AcceptSet, len(types))
+ for _, t := range types {
+ m[t] = struct{}{}
+ }
+ return m
+}
+
+// Accepts returns true iff txType is accepted by this CeloValidationOptions.
+func (cvo *CeloValidationOptions) Accepts(txType uint8) bool {
+ _, ok := cvo.AcceptSet[txType]
+ return ok
+}
+
+// CeloValidateTransaction is a helper method to check whether a transaction is valid
+// according to the consensus rules, but does not check state-dependent validation
+// (balance, nonce, etc).
+//
+// This check is public to allow different transaction pools to check the basic
+// rules without duplicating code and running the risk of missed updates.
+func CeloValidateTransaction(tx *types.Transaction, head *types.Header,
+ signer types.Signer, opts *CeloValidationOptions, currencyCtx common.FeeCurrencyContext) error {
+ if err := ValidateTransaction(tx, head, signer, opts, currencyCtx); err != nil {
+ return err
+ }
+
+ if !common.IsCurrencyAllowed(currencyCtx.ExchangeRates, tx.FeeCurrency()) {
+ return exchange.ErrUnregisteredFeeCurrency
+ }
+
+ if opts.Config.Celo != nil {
+ // Make sure that the effective gas tip at the base fee floor is at least the
+ // requested min-tip.
+ // The min-tip for local transactions is set to 0, we can skip checking here.
+ if opts.MinTip != nil && opts.MinTip.Cmp(new(big.Int)) > 0 {
+ // If not, this would never be included, so we can reject early.
+ minTip, err := exchange.ConvertCeloToCurrency(currencyCtx.ExchangeRates, tx.FeeCurrency(), opts.MinTip)
+ if err != nil {
+ return err
+ }
+ baseFeeFloor, err := exchange.ConvertCeloToCurrency(currencyCtx.ExchangeRates, tx.FeeCurrency(), new(big.Int).SetUint64(opts.Config.Celo.EIP1559BaseFeeFloor))
+ if err != nil {
+ return err
+ }
+ if tx.EffectiveGasTipIntCmp(minTip, baseFeeFloor) < 0 {
+ return ErrUnderpriced
+ }
+ }
+
+ celoGasPrice, err := exchange.ConvertCurrencyToCelo(
+ currencyCtx.ExchangeRates,
+ tx.FeeCurrency(),
+ tx.GasFeeCap(),
+ )
+ if err != nil {
+ return err
+ }
+
+ if new(big.Int).SetUint64(opts.Config.Celo.EIP1559BaseFeeFloor).Cmp(celoGasPrice) == 1 {
+ return ErrGasPriceDoesNotExceedBaseFeeFloor
+ }
+ }
+ return nil
+}
diff --git op-geth/core/txpool/legacypool/celo_legacypool.go Celo/core/txpool/legacypool/celo_legacypool.go
new file mode 100644
index 0000000000000000000000000000000000000000..a6f4da7a2ba8874ecf70e055188f66bf256db896
--- /dev/null
+++ Celo/core/txpool/legacypool/celo_legacypool.go
@@ -0,0 +1,43 @@
+package legacypool
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/holiman/uint256"
+)
+
+// filter Filters transactions from the given list, according to remaining balance (per currency)
+// and gasLimit. Returns drops and invalid txs.
+func (pool *LegacyPool) filter(list *list, addr common.Address, gasLimit uint64) (types.Transactions, types.Transactions) {
+ // CELO: drop all transactions that no longer have a registered currency
+ dropsAllowlist, invalidsAllowlist := list.FilterAllowlisted(pool.feeCurrencyContext.ExchangeRates)
+ // Check from which currencies we need to get balances
+ currenciesInList := list.FeeCurrencies()
+ drops, invalids := list.Filter(pool.getBalances(addr, currenciesInList), gasLimit)
+ totalDrops := append(dropsAllowlist, drops...)
+ totalInvalids := append(invalidsAllowlist, invalids...)
+ return totalDrops, totalInvalids
+}
+
+func (pool *LegacyPool) getBalances(address common.Address, currencies []common.Address) map[common.Address]*uint256.Int {
+ balances := make(map[common.Address]*uint256.Int, len(currencies))
+ for _, curr := range currencies {
+ balances[curr] = uint256.MustFromBig(contracts.GetFeeBalance(pool.celoBackend, address, &curr))
+ }
+ return balances
+}
+
+func (pool *LegacyPool) recreateCeloProperties() {
+ pool.celoBackend = &contracts.CeloBackend{
+ ChainConfig: pool.chainconfig,
+ State: pool.currentState,
+ }
+ feeCurrencyContext, err := contracts.GetFeeCurrencyContext(pool.celoBackend)
+ if err != nil {
+ log.Error("Error trying to get fee currency context in txpool.", "cause", err)
+ }
+
+ pool.feeCurrencyContext = feeCurrencyContext
+}
diff --git op-geth/core/txpool/legacypool/celo_legacypool_test.go Celo/core/txpool/legacypool/celo_legacypool_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..88a694a74c90c933c9761360d4aa6e9f185f6e50
--- /dev/null
+++ Celo/core/txpool/legacypool/celo_legacypool_test.go
@@ -0,0 +1,205 @@
+package legacypool
+
+import (
+ "crypto/ecdsa"
+ "errors"
+ "math/big"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/txpool"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/triedb"
+)
+
+func celoConfig(baseFeeFloor uint64) *params.ChainConfig {
+ cpy := *params.TestChainConfig
+ config := &cpy
+ ct := uint64(0)
+ config.Cel2Time = &ct
+ config.Celo = ¶ms.CeloConfig{EIP1559BaseFeeFloor: baseFeeFloor}
+ return config
+}
+
+var (
+ // worth half as much as native celo
+ feeCurrencyOne = core.DevFeeCurrencyAddr
+ // worth twice as much as native celo
+ feeCurrencyTwo = core.DevFeeCurrencyAddr2
+ feeCurrencyIntrinsicGas = core.FeeCurrencyIntrinsicGas
+ defaultBaseFeeFloor = 100
+ defaultChainConfig = celoConfig(uint64(defaultBaseFeeFloor))
+)
+
+func pricedCip64Transaction(
+ config *params.ChainConfig,
+ nonce uint64,
+ gasLimit uint64,
+ gasFeeCap *big.Int,
+ gasTipCap *big.Int,
+ feeCurrency *common.Address,
+ key *ecdsa.PrivateKey,
+) *types.Transaction {
+ tx, _ := types.SignTx(types.NewTx(&types.CeloDynamicFeeTxV2{
+ Nonce: nonce,
+ To: &common.Address{},
+ Value: big.NewInt(100),
+ Gas: gasLimit,
+ GasFeeCap: gasFeeCap,
+ GasTipCap: gasTipCap,
+ FeeCurrency: feeCurrency,
+ Data: nil,
+ }), types.LatestSigner(config), key)
+ return tx
+}
+
+func newDBWithCeloGenesis(config *params.ChainConfig, fundedAddress common.Address) (state.Database, *types.Block) {
+ gspec := &core.Genesis{
+ Config: config,
+ Alloc: core.CeloGenesisAccounts(fundedAddress),
+ }
+ db := rawdb.NewMemoryDatabase()
+ triedb := triedb.NewDatabase(db, triedb.HashDefaults)
+ defer triedb.Close()
+ block, err := gspec.Commit(db, triedb)
+ if err != nil {
+ panic(err)
+ }
+ return state.NewDatabase(triedb, nil), block
+}
+
+func setupCeloPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) {
+ key, _ := crypto.GenerateKey()
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+
+ ddb, genBlock := newDBWithCeloGenesis(config, addr)
+ stateRoot := genBlock.Header().Root
+ statedb, err := state.New(stateRoot, ddb)
+ if err != nil {
+ panic(err)
+ }
+ blockchain := newTestBlockChain(config, 10000000, statedb, new(event.Feed))
+ pool := New(testTxPoolConfig, blockchain)
+
+ block := blockchain.CurrentBlock()
+ // inject the state-root from the genesis chain, so
+ // that the fee-currency allocs are accessible from the state
+ // and can be used to create the fee-currency context in the txpool
+ block.Root = stateRoot
+ if err := pool.Init(testTxPoolConfig.PriceLimit, block, makeAddressReserver()); err != nil {
+ panic(err)
+ }
+ // wait for the pool to initialize
+ <-pool.initDoneCh
+ return pool, key
+}
+
+func TestBelowBaseFeeFloorValidityCheck(t *testing.T) {
+ t.Parallel()
+
+ pool, key := setupCeloPoolWithConfig(defaultChainConfig)
+ defer pool.Close()
+
+ // gas-price below base-fee-floor should return early
+ // and thus raise an error in the validation
+
+ // use local transactions here to skip the min-tip conversion
+ // the PriceLimit config is set to 1, so we need at least a tip of 1
+ tx := pricedCip64Transaction(defaultChainConfig, 0, 21000, big.NewInt(99), big.NewInt(0), nil, key)
+ if err, want := pool.addLocal(tx), txpool.ErrGasPriceDoesNotExceedBaseFeeFloor; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+ // also test with fee currency conversion
+ tx = pricedCip64Transaction(defaultChainConfig, 0, 21000+feeCurrencyIntrinsicGas, big.NewInt(198), big.NewInt(0), &feeCurrencyOne, key)
+ if err, want := pool.addLocal(tx), txpool.ErrGasPriceDoesNotExceedBaseFeeFloor; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+ tx = pricedCip64Transaction(defaultChainConfig, 0, 21000+feeCurrencyIntrinsicGas, big.NewInt(48), big.NewInt(0), &feeCurrencyTwo, key)
+ if err, want := pool.addLocal(tx), txpool.ErrGasPriceDoesNotExceedBaseFeeFloor; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+}
+
+func TestAboveBaseFeeFloorValidityCheck(t *testing.T) {
+ t.Parallel()
+
+ pool, key := setupCeloPoolWithConfig(defaultChainConfig)
+ defer pool.Close()
+
+ // gas-price just at base-fee-floor should be valid,
+ // this also adds the required min-tip of 1
+ tx := pricedCip64Transaction(defaultChainConfig, 0, 21000, big.NewInt(101), big.NewInt(1), nil, key)
+ assert.NoError(t, pool.addRemote(tx))
+ // also test with fee currency conversion, increase nonce because of previous tx was valid
+ tx = pricedCip64Transaction(defaultChainConfig, 1, 21000+feeCurrencyIntrinsicGas, big.NewInt(202), big.NewInt(2), &feeCurrencyOne, key)
+ assert.NoError(t, pool.addRemote(tx))
+ tx = pricedCip64Transaction(defaultChainConfig, 2, 21000+feeCurrencyIntrinsicGas, big.NewInt(51), big.NewInt(1), &feeCurrencyTwo, key)
+ assert.NoError(t, pool.addRemote(tx))
+}
+
+func TestBelowMinTipValidityCheck(t *testing.T) {
+ t.Parallel()
+
+ pool, key := setupCeloPoolWithConfig(defaultChainConfig)
+ defer pool.Close()
+
+ // the min-tip is set to 1 per default
+
+ // Gas-price just at base-fee-floor should be valid,
+ // the effective gas-price would also pass the min-tip restriction of 1.
+ // However the explicit gas-tip-cap at 0 should reject the transaction.
+ tx := pricedCip64Transaction(defaultChainConfig, 0, 21000, big.NewInt(101), big.NewInt(0), nil, key)
+ if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+ tx = pricedCip64Transaction(defaultChainConfig, 0, 21000+feeCurrencyIntrinsicGas, big.NewInt(202), big.NewInt(0), &feeCurrencyOne, key)
+ if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+
+ // This passes the check that only checks the actual gas-tip-cap value for the min-tip that was
+ // tested above.
+ // Now the effective gas-tip should still be below the min-tip, since we consume everything
+ // for the base fee floor and thus the tx should get rejected.
+ tx = pricedCip64Transaction(defaultChainConfig, 0, 21000, big.NewInt(100), big.NewInt(1), nil, key)
+ if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+ tx = pricedCip64Transaction(defaultChainConfig, 0, 21000+feeCurrencyIntrinsicGas, big.NewInt(200), big.NewInt(2), &feeCurrencyOne, key)
+ if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+}
+
+func TestExpectMinTipRoundingFeeCurrency(t *testing.T) {
+ t.Parallel()
+
+ pool, key := setupCeloPoolWithConfig(defaultChainConfig)
+ defer pool.Close()
+
+ // the min-tip is set to 1 per default
+
+ // even though the gas-tip-cap as well as the effective gas tip at the base-fee-floor
+ // is 0, the transaction is still accepted.
+ // This is because at a min-tip requirement of 1, a more valuable currency than native
+ // token will get rounded down to a min-tip of 0 during conversion.
+ tx := pricedCip64Transaction(defaultChainConfig, 0, 21000+feeCurrencyIntrinsicGas, big.NewInt(50), big.NewInt(0), &feeCurrencyTwo, key)
+ assert.NoError(t, pool.addRemote(tx))
+
+ // set the required min-tip to 10
+ pool.SetGasTip(big.NewInt(10))
+
+ // but as soon as we increase the min-tip, the check rejects a gas-tip-cap that is too low after conversion
+ tx = pricedCip64Transaction(defaultChainConfig, 0, 21000+feeCurrencyIntrinsicGas, big.NewInt(100), big.NewInt(4), &feeCurrencyTwo, key)
+ if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
+ t.Errorf("want %v have %v", want, err)
+ }
+}
diff --git op-geth/core/txpool/legacypool/celo_list.go Celo/core/txpool/legacypool/celo_list.go
new file mode 100644
index 0000000000000000000000000000000000000000..b1c5594e743fbab0c52ea00e0c6d79aea1b8edc9
--- /dev/null
+++ Celo/core/txpool/legacypool/celo_list.go
@@ -0,0 +1,134 @@
+package legacypool
+
+import (
+ "math"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/holiman/uint256"
+)
+
+func (l *list) FilterAllowlisted(rates common.ExchangeRates) (types.Transactions, types.Transactions) {
+ removed := l.txs.Filter(func(tx *types.Transaction) bool {
+ return !common.IsCurrencyAllowed(rates, tx.FeeCurrency())
+ })
+
+ if len(removed) == 0 {
+ return nil, nil
+ }
+
+ invalid := l.dropInvalidsAfterRemovalAndReheap(removed)
+ l.subTotalCost(removed)
+ l.subTotalCost(invalid)
+ return removed, invalid
+}
+
+func (l *list) dropInvalidsAfterRemovalAndReheap(removed types.Transactions) types.Transactions {
+ var invalids types.Transactions
+ // If the list was strict, filter anything above the lowest nonce
+ // Note that the 'invalid' txs have no intersection with the 'removed' txs
+ if l.strict {
+ lowest := uint64(math.MaxUint64)
+ for _, tx := range removed {
+ if nonce := tx.Nonce(); lowest > nonce {
+ lowest = nonce
+ }
+ }
+ invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
+ }
+ l.txs.reheap()
+ return invalids
+}
+
+func (l *list) FeeCurrencies() []common.Address {
+ currencySet := make(map[common.Address]interface{})
+ currencySet[getCurrencyKey(nil)] = struct{}{} // Always include native token to handle potential value transfers
+ for _, tx := range l.txs.items {
+ // native currency (nil) represented as Zero address
+ currencySet[getCurrencyKey(tx.FeeCurrency())] = struct{}{}
+ }
+ currencies := make([]common.Address, 0, len(currencySet))
+ for curr := range currencySet {
+ currencies = append(currencies, curr)
+ }
+ return currencies
+}
+
+func getCurrencyKey(feeCurrency *common.Address) common.Address {
+ if feeCurrency == nil {
+ return common.ZeroAddress
+ }
+ return *feeCurrency
+}
+
+func (l *list) totalCostVar(feeCurrency *common.Address) *uint256.Int {
+ key := getCurrencyKey(feeCurrency)
+ if tc, ok := l.totalCost[key]; ok {
+ return tc
+ }
+ newTc := new(uint256.Int)
+ l.totalCost[key] = newTc
+ return newTc
+}
+
+func (l *list) TotalCostFor(feeCurrency *common.Address) *uint256.Int {
+ if tc, ok := l.totalCost[getCurrencyKey(feeCurrency)]; ok {
+ return new(uint256.Int).Set(tc)
+ }
+ return new(uint256.Int)
+}
+
+func (l *list) costCapFor(feeCurrency *common.Address) *uint256.Int {
+ if tc, ok := l.costCap[getCurrencyKey(feeCurrency)]; ok {
+ return tc
+ }
+ return new(uint256.Int)
+}
+
+func (l *list) updateCostCapFor(feeCurrency *common.Address, possibleCap *uint256.Int) {
+ currentCap := l.costCapFor(feeCurrency)
+ if possibleCap.Cmp(currentCap) > 0 {
+ l.costCap[getCurrencyKey(feeCurrency)] = possibleCap
+ }
+}
+
+func (l *list) costCapsLowerThan(costLimits map[common.Address]*uint256.Int) bool {
+ for curr, cap := range l.costCap {
+ limit, ok := costLimits[curr]
+ if !ok || limit == nil {
+ // If there's no limit for the currency we can assume the limit is zero
+ return cap.IsZero()
+ }
+ if cap.Cmp(limit) > 0 {
+ return false
+ }
+ }
+ return true
+}
+
+func (l *list) setCapsTo(caps map[common.Address]*uint256.Int) {
+ l.costCap = make(map[common.Address]*uint256.Int)
+ for curr, cap := range caps {
+ if cap == nil || cap.IsZero() {
+ l.costCap[curr] = new(uint256.Int)
+ } else {
+ l.costCap[curr] = new(uint256.Int).Set(cap)
+ }
+ }
+}
+
+// GetNativeBaseFee returns the base fee for this priceHeap
+func (h *priceHeap) GetNativeBaseFee() *big.Int {
+ if h.ratesAndFees == nil {
+ return nil
+ }
+ return h.ratesAndFees.GetNativeBaseFee()
+}
+
+func (h *priceHeap) GetBaseFeeIn(feeCurrency *common.Address) *big.Int {
+ if h.ratesAndFees == nil {
+ return nil
+ }
+ return h.ratesAndFees.GetBaseFeeIn(feeCurrency)
+}
diff --git op-geth/core/txpool/legacypool/celo_list_test.go Celo/core/txpool/legacypool/celo_list_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..86782f41ded861297605af14b37556b510b96c0e
--- /dev/null
+++ Celo/core/txpool/legacypool/celo_list_test.go
@@ -0,0 +1,199 @@
+package legacypool
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/holiman/uint256"
+ "github.com/stretchr/testify/assert"
+)
+
+func txC(nonce int, feeCap int, tipCap int, gas int, currency *common.Address) *types.Transaction {
+ return types.NewTx(&types.CeloDynamicFeeTxV2{
+ GasFeeCap: big.NewInt(int64(feeCap)),
+ GasTipCap: big.NewInt(int64(tipCap)),
+ FeeCurrency: currency,
+ Gas: uint64(gas),
+ Nonce: uint64(nonce),
+ })
+}
+
+func TestListFeeCost(t *testing.T) {
+ curr1 := common.HexToAddress("0002")
+ curr2 := common.HexToAddress("0004")
+ curr3 := common.HexToAddress("0006")
+ rates := common.ExchangeRates{
+ curr1: big.NewRat(2, 1),
+ curr2: big.NewRat(4, 1),
+ curr3: big.NewRat(6, 1),
+ }
+ // Insert the transactions in a random order
+ list := newList(false)
+
+ list.Add(txC(7, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ assert.Equal(t, uint64(10000), list.TotalCostFor(&curr1).Uint64())
+
+ toBeRemoved := txC(8, 2, 1, 15000, &curr2)
+ list.Add(toBeRemoved, DefaultConfig.PriceBump, nil, rates)
+ assert.Equal(t, uint64(30000), list.TotalCostFor(&curr2).Uint64())
+ assert.Equal(t, uint64(10000), list.TotalCostFor(&curr1).Uint64())
+
+ list.Add(txC(9, 3, 2, 5000, &curr3), DefaultConfig.PriceBump, nil, rates)
+ assert.Equal(t, uint64(15000), list.TotalCostFor(&curr3).Uint64())
+ assert.Equal(t, uint64(30000), list.TotalCostFor(&curr2).Uint64())
+ assert.Equal(t, uint64(10000), list.TotalCostFor(&curr1).Uint64())
+
+ // Add another tx from curr1, check it adds properly
+ list.Add(txC(10, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ assert.Equal(t, uint64(15000), list.TotalCostFor(&curr3).Uint64())
+ assert.Equal(t, uint64(30000), list.TotalCostFor(&curr2).Uint64())
+ assert.Equal(t, uint64(20000), list.TotalCostFor(&curr1).Uint64())
+
+ // Remove a tx from curr2, check it subtracts properly
+ removed, _ := list.Remove(toBeRemoved)
+ assert.True(t, removed)
+
+ assert.Equal(t, uint64(15000), list.TotalCostFor(&curr3).Uint64())
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr2).Uint64())
+ assert.Equal(t, uint64(20000), list.TotalCostFor(&curr1).Uint64())
+}
+
+func TestFilterAllowlisted(t *testing.T) {
+ curr1 := common.HexToAddress("0002")
+ curr2 := common.HexToAddress("0004")
+ curr3 := common.HexToAddress("0006")
+ rates := common.ExchangeRates{
+ curr1: big.NewRat(2, 1),
+ curr2: big.NewRat(4, 1),
+ curr3: big.NewRat(6, 1),
+ }
+
+ list := newList(false)
+ list.Add(txC(7, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ toBeRemoved := txC(8, 2, 1, 15000, &curr2)
+ list.Add(toBeRemoved, DefaultConfig.PriceBump, nil, rates)
+ list.Add(txC(9, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ assert.Equal(t, uint64(30000), list.TotalCostFor(&curr2).Uint64())
+
+ removed, invalids := list.FilterAllowlisted(common.ExchangeRates{curr1: nil, curr3: nil})
+ assert.Len(t, removed, 1)
+ assert.Len(t, invalids, 0)
+ assert.Equal(t, removed[0], toBeRemoved)
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr2).Uint64())
+}
+
+func TestFilterAllowlistedStrict(t *testing.T) {
+ curr1 := common.HexToAddress("0002")
+ curr2 := common.HexToAddress("0004")
+ curr3 := common.HexToAddress("0006")
+ rates := common.ExchangeRates{
+ curr1: big.NewRat(2, 1),
+ curr2: big.NewRat(4, 1),
+ curr3: big.NewRat(6, 1),
+ }
+
+ list := newList(true)
+ list.Add(txC(7, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ toBeRemoved := txC(8, 2, 1, 15000, &curr2)
+ list.Add(toBeRemoved, DefaultConfig.PriceBump, nil, rates)
+ toBeInvalid := txC(9, 1, 1, 10000, &curr3)
+ list.Add(toBeInvalid, DefaultConfig.PriceBump, nil, rates)
+
+ removed, invalids := list.FilterAllowlisted(common.ExchangeRates{curr1: nil, curr3: nil})
+ assert.Len(t, removed, 1)
+ assert.Len(t, invalids, 1)
+ assert.Equal(t, removed[0], toBeRemoved)
+ assert.Equal(t, invalids[0], toBeInvalid)
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr2).Uint64())
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr3).Uint64())
+ assert.Equal(t, uint64(10000), list.TotalCostFor(&curr1).Uint64())
+}
+
+func TestFilterBalance(t *testing.T) {
+ curr1 := common.HexToAddress("0002")
+ curr2 := common.HexToAddress("0004")
+ curr3 := common.HexToAddress("0006")
+ rates := common.ExchangeRates{
+ curr1: big.NewRat(2, 1),
+ curr2: big.NewRat(4, 1),
+ curr3: big.NewRat(6, 1),
+ }
+
+ list := newList(false)
+ // each tx costs 10000 in each currency
+ list.Add(txC(7, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ toBeRemoved := txC(8, 1, 1, 10000, &curr2)
+ list.Add(toBeRemoved, DefaultConfig.PriceBump, nil, rates)
+ list.Add(txC(9, 1, 1, 10000, &curr3), DefaultConfig.PriceBump, nil, rates)
+
+ removed, invalids := list.Filter(map[common.Address]*uint256.Int{
+ curr1: uint256.NewInt(10000),
+ curr2: uint256.NewInt(9999),
+ curr3: uint256.NewInt(10000),
+ }, 15000)
+ assert.Len(t, removed, 1)
+ assert.Len(t, invalids, 0)
+ assert.Equal(t, removed[0], toBeRemoved)
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr2).Uint64())
+}
+
+func TestFilterBalanceStrict(t *testing.T) {
+ curr1 := common.HexToAddress("0002")
+ curr2 := common.HexToAddress("0004")
+ curr3 := common.HexToAddress("0006")
+ rates := common.ExchangeRates{
+ curr1: big.NewRat(2, 1),
+ curr2: big.NewRat(4, 1),
+ curr3: big.NewRat(6, 1),
+ }
+
+ list := newList(true)
+ // each tx costs 10000 in each currency
+ list.Add(txC(7, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ toBeRemoved := txC(8, 1, 1, 10000, &curr2)
+ list.Add(toBeRemoved, DefaultConfig.PriceBump, nil, rates)
+ toBeInvalid := txC(9, 1, 1, 10000, &curr3)
+ list.Add(toBeInvalid, DefaultConfig.PriceBump, nil, rates)
+
+ removed, invalids := list.Filter(map[common.Address]*uint256.Int{
+ curr1: uint256.NewInt(10001),
+ curr2: uint256.NewInt(9999),
+ curr3: uint256.NewInt(10001),
+ }, 15000)
+ assert.Len(t, removed, 1)
+ assert.Len(t, invalids, 1)
+ assert.Equal(t, removed[0], toBeRemoved)
+ assert.Equal(t, invalids[0], toBeInvalid)
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr2).Uint64())
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr3).Uint64())
+}
+
+func TestFilterBalanceGasLimit(t *testing.T) {
+ curr1 := common.HexToAddress("0002")
+ curr2 := common.HexToAddress("0004")
+ curr3 := common.HexToAddress("0006")
+ rates := common.ExchangeRates{
+ curr1: big.NewRat(2, 1),
+ curr2: big.NewRat(4, 1),
+ curr3: big.NewRat(6, 1),
+ }
+
+ list := newList(false)
+ // each tx costs 10000 in each currency
+ list.Add(txC(7, 1, 1, 10000, &curr1), DefaultConfig.PriceBump, nil, rates)
+ toBeRemoved := txC(8, 1, 1, 10001, &curr2)
+ list.Add(toBeRemoved, DefaultConfig.PriceBump, nil, rates)
+ list.Add(txC(9, 1, 1, 10000, &curr3), DefaultConfig.PriceBump, nil, rates)
+
+ removed, invalids := list.Filter(map[common.Address]*uint256.Int{
+ curr1: uint256.NewInt(20000),
+ curr2: uint256.NewInt(20000),
+ curr3: uint256.NewInt(20000),
+ }, 10000)
+ assert.Len(t, removed, 1)
+ assert.Len(t, invalids, 0)
+ assert.Equal(t, removed[0], toBeRemoved)
+ assert.Equal(t, uint64(0), list.TotalCostFor(&curr2).Uint64())
+}
diff --git op-geth/core/txpool/legacypool/legacypool.go Celo/core/txpool/legacypool/legacypool.go
index 2579104e5a4e1aae06f9f3f9da0ddd50600df506..c73c95b052817d10519bcee9902a917d7ff21134 100644
--- op-geth/core/txpool/legacypool/legacypool.go
+++ Celo/core/txpool/legacypool/legacypool.go
@@ -19,6 +19,7 @@ package legacypool
import (
"errors"
+ "fmt"
"math"
"math/big"
"sort"
@@ -27,8 +28,10 @@ "sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
+ "github.com/ethereum/go-ethereum/contracts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
@@ -102,6 +105,16 @@ localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
+
+ // Celo specific metrics
+ validTxMeterByTxType = map[byte]metrics.Meter{
+ types.LegacyTxType: metrics.NewRegisteredMeter("txpool/txtype/legacy", nil),
+ types.AccessListTxType: metrics.NewRegisteredMeter("txpool/txtype/accesslist", nil),
+ types.DynamicFeeTxType: metrics.NewRegisteredMeter("txpool/txtype/dynamicfee", nil),
+ types.BlobTxType: metrics.NewRegisteredMeter("txpool/txtype/blob", nil),
+ types.CeloDynamicFeeTxV2Type: metrics.NewRegisteredMeter("txpool/txtype/cip64", nil),
+ }
+ validTxMeterByFeeCurrency = map[common.Address]metrics.Meter{}
)
// BlockChain defines the minimal set of methods needed to back a tx pool with
@@ -240,6 +253,10 @@
changesSinceReorg int // A counter for how many drops we've performed in-between reorg.
l1CostFn txpool.L1CostFunc // To apply L1 costs as rollup, optional field, may be nil.
+
+ // Celo specific
+ celoBackend *contracts.CeloBackend // For fee currency balances & exchange rate calculation
+ feeCurrencyContext common.FeeCurrencyContext // context for fee currencies
}
type txpoolResetRequest struct {
@@ -286,7 +303,7 @@ // Filter returns whether the given transaction can be consumed by the legacy
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
switch tx.Type() {
- case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
+ case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxV2Type:
return true
default:
return false
@@ -317,6 +334,7 @@ }
pool.currentHead.Store(head)
pool.currentState = statedb
pool.pendingNonces = newNoncer(statedb)
+ pool.recreateCeloProperties()
// Start the reorg loop early, so it can handle requests generated during
// journal loading.
@@ -546,15 +564,11 @@ defer pool.mu.Unlock()
// Convert the new uint256.Int types to the old big.Int ones used by the legacy pool
var (
- minTipBig *big.Int
- baseFeeBig *big.Int
+ minTipBig *big.Int
)
if filter.MinTip != nil {
minTipBig = filter.MinTip.ToBig()
}
- if filter.BaseFee != nil {
- baseFeeBig = filter.BaseFee.ToBig()
- }
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
for addr, list := range pool.pending {
txs := list.Flatten()
@@ -562,7 +576,8 @@
// If the miner requests tip enforcement, cap the lists now
if minTipBig != nil && !pool.locals.contains(addr) {
for i, tx := range txs {
- if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 {
+ minTipInFeeCurrency, err := exchange.ConvertCeloToCurrency(pool.feeCurrencyContext.ExchangeRates, tx.FeeCurrency(), minTipBig)
+ if err != nil || tx.EffectiveGasTipIntCmp(minTipInFeeCurrency, pool.priced.urgent.GetBaseFeeIn(tx.FeeCurrency())) < 0 {
txs = txs[:i]
break
}
@@ -593,6 +608,9 @@ GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()),
Gas: txs[i].Gas(),
BlobGas: txs[i].BlobGas(),
DABytes: daBytes,
+
+ // Celo specific
+ FeeCurrency: txs[i].FeeCurrency(),
}
}
pending[addr] = lazies
@@ -647,12 +665,13 @@ // rules, but does not check state-dependent validation such as sufficient balance.
// This check is meant as an early check which only needs to be performed once,
// and does not require the pool mutex to be held.
func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) error {
- opts := &txpool.ValidationOptions{
+ opts := &txpool.CeloValidationOptions{
Config: pool.chainconfig,
- Accept: 0 |
- 1<<types.LegacyTxType |
- 1<<types.AccessListTxType |
- 1<<types.DynamicFeeTxType,
+ AcceptSet: txpool.NewAcceptSet(
+ types.LegacyTxType,
+ types.AccessListTxType,
+ types.DynamicFeeTxType,
+ types.CeloDynamicFeeTxV2Type),
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
EffectiveGasCeil: pool.config.EffectiveGasCeil,
@@ -660,7 +679,7 @@ }
if local {
opts.MinTip = new(big.Int)
}
- if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil {
+ if err := txpool.CeloValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts, pool.feeCurrencyContext); err != nil {
return err
}
return nil
@@ -683,31 +702,68 @@ have += list.Len()
}
return have, math.MaxInt
},
- ExistingExpenditure: func(addr common.Address) *big.Int {
+ ExistingExpenditure: func(addr common.Address) (*big.Int, *big.Int) {
if list := pool.pending[addr]; list != nil {
- return list.totalcost.ToBig()
+ if tx.FeeCurrency() != nil {
+ return list.TotalCostFor(tx.FeeCurrency()).ToBig(), list.TotalCostFor(nil).ToBig()
+ } else {
+ return common.Big0, list.TotalCostFor(nil).ToBig()
+ }
}
- return new(big.Int)
+ return new(big.Int), new(big.Int)
},
- ExistingCost: func(addr common.Address, nonce uint64) *big.Int {
+ ExistingCost: func(addr common.Address, nonce uint64) (*big.Int, *big.Int) {
if list := pool.pending[addr]; list != nil {
+ feeCurrency := tx.FeeCurrency()
if tx := list.txs.Get(nonce); tx != nil {
- cost := tx.Cost()
+ feeCurrencyCost, nativeCost := tx.Cost()
if pool.l1CostFn != nil {
if l1Cost := pool.l1CostFn(tx.RollupCostData()); l1Cost != nil { // add rollup cost
- cost = cost.Add(cost, l1Cost)
+ nativeCost = nativeCost.Add(nativeCost, l1Cost)
}
}
- return cost
+ if !common.AreSameAddress(tx.FeeCurrency(), feeCurrency) {
+ // We are only interested in costs in the same currency
+ feeCurrencyCost = new(big.Int)
+ }
+ return feeCurrencyCost, nativeCost
}
}
- return nil
+ return nil, nil
},
L1CostFn: pool.l1CostFn,
+ ExistingBalance: func(addr common.Address, feeCurrency *common.Address) *big.Int {
+ return contracts.GetFeeBalance(pool.celoBackend, addr, feeCurrency)
+ },
}
+
if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil {
return err
}
+ if tx.FeeCurrency() != nil {
+ from, err := pool.signer.Sender(tx) // already validated (and cached), but cleaner to check
+ if err != nil {
+ log.Error("Transaction sender recovery failed", "err", err)
+ return err
+ }
+ //NOTE: we only test the `debitFees` call here.
+ // If the `creditFees` reverts (or runs out of gas),
+ // the transaction reverts within the STF. This results in the user's
+ // transaction being computed free of charge. In order to mitigate this,
+ // the txpool worker keeps track of fee-currencies where this happens and
+ // limits the impact this can have on resources by adding them to a blocklist
+ // for a limited time.
+ // Note however that the fee-currency blocklist is kept downstream,
+ // so the TryDebitFees call below will be executed even for blocklisted fee-currencies.
+ err = contracts.TryDebitFees(tx, from, pool.celoBackend, pool.feeCurrencyContext)
+ if errors.Is(err, contracts.ErrFeeCurrencyEVMCall) {
+ log.Error("executing debit fees in legacypool validation failed", "err", err)
+ // make the error message less specific / verbose,
+ // this will get returned by the `eth_sendRawTransaction` call
+ err = fmt.Errorf("fee-currency internal error")
+ }
+ return err
+ }
return nil
}
@@ -826,7 +882,7 @@
// Try to replace an existing transaction in the pending pool
if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) {
// Nonce already pending, check if required price bump is met
- inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn)
+ inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.feeCurrencyContext.ExchangeRates)
if !inserted {
pendingDiscardMeter.Mark(1)
return false, txpool.ErrReplaceUnderpriced
@@ -900,7 +956,7 @@ from, _ := types.Sender(pool.signer, tx) // already validated
if pool.queue[from] == nil {
pool.queue[from] = newList(false)
}
- inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, pool.l1CostFn)
+ inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.feeCurrencyContext.ExchangeRates)
if !inserted {
// An older transaction was better, discard this
queuedDiscardMeter.Mark(1)
@@ -954,7 +1010,7 @@ pool.pending[addr] = newList(true)
}
list := pool.pending[addr]
- inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn)
+ inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.feeCurrencyContext.ExchangeRates)
if !inserted {
// An older transaction was better, discard this
pool.all.Remove(hash)
@@ -1087,6 +1143,19 @@ replaced, err := pool.add(tx, local)
errs[i] = err
if err == nil && !replaced {
dirty.addTx(tx)
+ if tx.FeeCurrency() != nil {
+ feeCurrencyMeter, ok := validTxMeterByFeeCurrency[*tx.FeeCurrency()]
+ if !ok {
+ feeCurrencyMeter = metrics.NewRegisteredMeter(
+ "txpool/feecurrency/"+tx.FeeCurrency().Hex(),
+ nil)
+ validTxMeterByFeeCurrency[*tx.FeeCurrency()] = feeCurrencyMeter
+ }
+ feeCurrencyMeter.Mark(1)
+ }
+ if txTypeMeter, ok := validTxMeterByTxType[tx.Type()]; ok {
+ txTypeMeter.Mark(1)
+ }
}
}
validTxMeter.Mark(int64(len(dirty.accounts)))
@@ -1350,7 +1419,7 @@ pool.demoteUnexecutables()
if reset.newHead != nil {
if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) {
pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead, reset.newHead.Time+1)
- pool.priced.SetBaseFee(pendingBaseFee)
+ pool.priced.SetBaseFeeAndRates(pendingBaseFee, pool.feeCurrencyContext.ExchangeRates)
} else {
pool.priced.Reheap()
}
@@ -1480,6 +1549,7 @@ }
pool.currentHead.Store(newHead)
pool.currentState = statedb
pool.pendingNonces = newNoncer(statedb)
+ pool.recreateCeloProperties()
if costFn := types.NewL1CostFunc(pool.chainconfig, statedb); costFn != nil {
pool.l1CostFn = func(rollupCostData types.RollupCostData) *big.Int {
@@ -1493,24 +1563,6 @@ core.SenderCacher.Recover(pool.signer, reinject)
pool.addTxsLocked(reinject, false)
}
-// reduceBalanceByL1Cost returns the given balance, reduced by the L1Cost of the first transaction in list if applicable
-// Other txs will get filtered out necessary.
-func (pool *LegacyPool) reduceBalanceByL1Cost(list *list, balance *uint256.Int) *uint256.Int {
- if !list.Empty() && pool.l1CostFn != nil {
- el := list.txs.FirstElement()
- if l1Cost := pool.l1CostFn(el.RollupCostData()); l1Cost != nil {
- l1Cost256 := uint256.MustFromBig(l1Cost)
- if l1Cost256.Cmp(balance) >= 0 {
- // Avoid underflow
- balance = uint256.NewInt(0)
- } else {
- balance = new(uint256.Int).Sub(balance, l1Cost256)
- }
- }
- }
- return balance
-}
-
// promoteExecutables moves transactions that have become processable from the
// future queue to the set of pending transactions. During this process, all
// invalidated transactions (low nonce, low balance) are deleted.
@@ -1532,10 +1584,9 @@ hash := tx.Hash()
pool.all.Remove(hash)
}
log.Trace("Removed old queued transactions", "count", len(forwards))
- balance := pool.currentState.GetBalance(addr)
- balance = pool.reduceBalanceByL1Cost(list, balance)
+
// Drop all transactions that are too costly (low balance or out of gas)
- drops, _ := list.Filter(balance, gasLimit)
+ drops, _ := pool.filter(list, addr, gasLimit)
for _, tx := range drops {
hash := tx.Hash()
pool.all.Remove(hash)
@@ -1735,10 +1786,9 @@ hash := tx.Hash()
pool.all.Remove(hash)
log.Trace("Removed old pending transaction", "hash", hash)
}
- balance := pool.currentState.GetBalance(addr)
- balance = pool.reduceBalanceByL1Cost(list, balance)
+
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
- drops, invalids := list.Filter(balance, gasLimit)
+ drops, invalids := pool.filter(list, addr, gasLimit)
for _, tx := range drops {
hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash)
diff --git op-geth/core/txpool/legacypool/legacypool_test.go Celo/core/txpool/legacypool/legacypool_test.go
index 2aecb3f2e7ddf3e80f4e898e00f8e67d19c9453d..10112b868e920d336eae817d83db753dd86edf8a 100644
--- op-geth/core/txpool/legacypool/legacypool_test.go
+++ Celo/core/txpool/legacypool/legacypool_test.go
@@ -199,6 +199,9 @@ }
if nonce := pool.pendingNonces.get(addr); nonce != last+1 {
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
}
+ if txs.TotalCostFor(nil).Cmp(new(uint256.Int)) < 0 {
+ return fmt.Errorf("totalcost went negative: %v", txs.TotalCostFor(nil))
+ }
}
return nil
}
@@ -2105,7 +2108,7 @@ }
add(false)
for baseFee = 0; baseFee <= 1000; baseFee += 100 {
- pool.priced.SetBaseFee(big.NewInt(int64(baseFee)))
+ pool.priced.SetBaseFeeAndRates(big.NewInt(int64(baseFee)), nil)
add(true)
check(highCap, "fee cap")
add(false)
@@ -2261,6 +2264,50 @@ t.Fatalf("queued replacement event firing failed: %v", err)
}
if err := validatePoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
+ }
+}
+
+// Tests that the pool accepts replacement transactions if the account only owns Celo.
+func TestCeloReplacement(t *testing.T) {
+ t.Parallel()
+
+ // Create the pool to test the pricing enforcement with
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
+
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver())
+ defer pool.Close()
+
+ // Keep track of transaction events to ensure all executables get announced
+ events := make(chan core.NewTxsEvent, 32)
+ sub := pool.txFeed.Subscribe(events)
+ defer sub.Unsubscribe()
+
+ // Create a test account to add transactions with
+ key, _ := crypto.GenerateKey()
+ balance := big.NewInt(6000000)
+ testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), balance)
+
+ // Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
+ price := int64(100)
+ priceBumped := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100
+
+ if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(price), key)); err != nil {
+ t.Fatalf("failed to add original cheap pending transaction: %v", err)
+ }
+ if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(priceBumped-1), key)); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
+ }
+ if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(priceBumped), key)); err != nil {
+ t.Fatalf("failed to replace original transaction: %v", err)
+ }
+ if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(price*2), key)); !errors.Is(err, core.ErrInsufficientFunds) {
+ txCost := big.NewInt(price*2*50000 + 100) // 100 is the amount default of the pricedTransaction function
+ t.Fatalf("second queue replacement error mismatch: have %v, want %v", err, fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, txCost, new(big.Int).Sub(txCost, balance)))
+ }
+ if err := validateEvents(events, 2); err != nil {
+ t.Fatalf("replacement event firing failed: %v", err)
}
}
diff --git op-geth/core/txpool/legacypool/list.go Celo/core/txpool/legacypool/list.go
index 24b18d2273252af4115825c94aaf6f9d8904dd1f..f8d0d399716fc5f7a2d23623d23ff9afcd2901f4 100644
--- op-geth/core/txpool/legacypool/list.go
+++ Celo/core/txpool/legacypool/list.go
@@ -18,7 +18,6 @@ package legacypool
import (
"container/heap"
- "math"
"math/big"
"slices"
"sort"
@@ -27,6 +26,7 @@ "sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
@@ -283,9 +283,9 @@ type list struct {
strict bool // Whether nonces are strictly continuous or not
txs *sortedMap // Heap indexed sorted hash map of the transactions
- costcap *uint256.Int // Price of the highest costing transaction (reset only if exceeds balance)
- gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
- totalcost *uint256.Int // Total cost of all transactions in the list
+ costCap map[common.Address]*uint256.Int // Price of the highest costing transaction per currency (reset only if exceeds balance)
+ gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
+ totalCost map[common.Address]*uint256.Int // Total cost of all transactions in the list (by currency)
}
// newList creates a new transaction list for maintaining nonce-indexable fast,
@@ -294,8 +294,8 @@ func newList(strict bool) *list {
return &list{
strict: strict,
txs: newSortedMap(),
- costcap: new(uint256.Int),
- totalcost: new(uint256.Int),
+ costCap: make(map[common.Address]*uint256.Int),
+ totalCost: make(map[common.Address]*uint256.Int),
}
}
@@ -310,11 +310,12 @@ // transaction was accepted, and if yes, any previous transaction it replaced.
//
// If the new transaction is accepted into the list, the lists' cost and gas
// thresholds are also potentially updated.
-func (l *list) Add(tx *types.Transaction, priceBump uint64, l1CostFn txpool.L1CostFunc) (bool, *types.Transaction) {
+func (l *list) Add(tx *types.Transaction, priceBump uint64, _ txpool.L1CostFunc, rates common.ExchangeRates) (bool, *types.Transaction) {
// If there's an older better transaction, abort
old := l.txs.Get(tx.Nonce())
if old != nil {
- if old.GasFeeCapCmp(tx) >= 0 || old.GasTipCapCmp(tx) >= 0 {
+ // Short circuit when it's clear that the new tx is worse
+ if common.AreSameAddress(old.FeeCurrency(), tx.FeeCurrency()) && (old.GasFeeCapCmp(tx) >= 0 || old.GasTipCapCmp(tx) >= 0) {
return false, nil
}
// thresholdFeeCap = oldFC * (100 + priceBump) / 100
@@ -327,31 +328,39 @@ b := big.NewInt(100)
thresholdFeeCap := aFeeCap.Div(aFeeCap, b)
thresholdTip := aTip.Div(aTip, b)
+ var thresholdFeeCapInCurrency = thresholdFeeCap
+ var thresholdTipInCurrency = thresholdTip
+ if tx.FeeCurrency() != old.FeeCurrency() {
+ thresholdFeeCapInCurrency = exchange.ConvertCurrency(rates, thresholdFeeCap, old.FeeCurrency(), tx.FeeCurrency())
+ thresholdTipInCurrency = exchange.ConvertCurrency(rates, thresholdTip, old.FeeCurrency(), tx.FeeCurrency())
+ }
// We have to ensure that both the new fee cap and tip are higher than the
// old ones as well as checking the percentage threshold to ensure that
// this is accurate for low (Wei-level) gas price replacements.
- if tx.GasFeeCapIntCmp(thresholdFeeCap) < 0 || tx.GasTipCapIntCmp(thresholdTip) < 0 {
+ if tx.GasFeeCapIntCmp(thresholdFeeCapInCurrency) < 0 || tx.GasTipCapIntCmp(thresholdTipInCurrency) < 0 {
return false, nil
}
// Old is being replaced, subtract old cost
l.subTotalCost([]*types.Transaction{old})
}
- // Add new tx cost to totalcost
- cost, overflow := uint256.FromBig(tx.Cost())
+ // Add new tx cost to total cost
+ feeCurrencyTc := l.totalCostVar(tx.FeeCurrency())
+ nativeTc := l.totalCostVar(&common.ZeroAddress)
+ feeCurrencyCostBig, nativeCostBig := tx.Cost()
+ feeCurrencyCost, overflow := uint256.FromBig(feeCurrencyCostBig)
if overflow {
return false, nil
}
- l.totalcost.Add(l.totalcost, cost)
- if l1CostFn != nil {
- if l1Cost := l1CostFn(tx.RollupCostData()); l1Cost != nil { // add rollup cost
- l.totalcost.Add(l.totalcost, cost)
- }
+ nativeCost, overflow := uint256.FromBig(nativeCostBig)
+ if overflow {
+ return false, nil
}
+ feeCurrencyTc.Add(feeCurrencyTc, feeCurrencyCost)
+ nativeTc.Add(nativeTc, nativeCost)
// Otherwise overwrite the old transaction with the current one
l.txs.Put(tx)
- if l.costcap.Cmp(cost) < 0 {
- l.costcap = cost
- }
+ l.updateCostCapFor(tx.FeeCurrency(), feeCurrencyCost)
+ l.updateCostCapFor(&common.ZeroAddress, nativeCost)
if gas := tx.Gas(); l.gascap < gas {
l.gascap = gas
}
@@ -376,33 +385,25 @@ // This method uses the cached costcap and gascap to quickly decide if there's even
// a point in calculating all the costs or if the balance covers all. If the threshold
// is lower than the costgas cap, the caps will be reset to a new high after removing
// the newly invalidated transactions.
-func (l *list) Filter(costLimit *uint256.Int, gasLimit uint64) (types.Transactions, types.Transactions) {
+func (l *list) Filter(costLimits map[common.Address]*uint256.Int, gasLimit uint64) (types.Transactions, types.Transactions) {
// If all transactions are below the threshold, short circuit
- if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
+ if l.costCapsLowerThan(costLimits) && l.gascap <= gasLimit {
return nil, nil
}
- l.costcap = new(uint256.Int).Set(costLimit) // Lower the caps to the thresholds
+ l.setCapsTo(costLimits) // Lower the caps to the thresholds
l.gascap = gasLimit
// Filter out all the transactions above the account's funds
removed := l.txs.Filter(func(tx *types.Transaction) bool {
- return tx.Gas() > gasLimit || tx.Cost().Cmp(costLimit.ToBig()) > 0
+ feeCurrencyCost, nativeCost := tx.Cost()
+ return tx.Gas() > gasLimit || feeCurrencyCost.Cmp(l.costCapFor(tx.FeeCurrency()).ToBig()) > 0 || nativeCost.Cmp(l.costCapFor(&common.ZeroAddress).ToBig()) > 0
})
if len(removed) == 0 {
return nil, nil
}
- var invalids types.Transactions
- // If the list was strict, filter anything above the lowest nonce
- if l.strict {
- lowest := uint64(math.MaxUint64)
- for _, tx := range removed {
- if nonce := tx.Nonce(); lowest > nonce {
- lowest = nonce
- }
- }
- invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
- }
+
+ invalids := l.dropInvalidsAfterRemovalAndReheap(removed)
// Reset total cost
l.subTotalCost(removed)
l.subTotalCost(invalids)
@@ -477,9 +478,16 @@ // subTotalCost subtracts the cost of the given transactions from the
// total cost of all transactions.
func (l *list) subTotalCost(txs []*types.Transaction) {
for _, tx := range txs {
- _, underflow := l.totalcost.SubOverflow(l.totalcost, uint256.MustFromBig(tx.Cost()))
+ feeCurrencyCost, nativeCost := tx.Cost()
+ feeCurrencyTc := l.totalCostVar(tx.FeeCurrency())
+ nativeTc := l.totalCostVar(&common.ZeroAddress)
+ _, underflow := feeCurrencyTc.SubOverflow(feeCurrencyTc, uint256.MustFromBig(feeCurrencyCost))
if underflow {
- panic("totalcost underflow")
+ panic("totalcost underflow (feecurrency)")
+ }
+ _, underflow = nativeTc.SubOverflow(nativeTc, uint256.MustFromBig(nativeCost))
+ if underflow {
+ panic("totalcost underflow (native)")
}
}
}
@@ -489,8 +497,11 @@ // price-sorted transactions to discard when the pool fills up. If baseFee is set
// then the heap is sorted based on the effective tip based on the given base fee.
// If baseFee is nil then the sorting is based on gasFeeCap.
type priceHeap struct {
- baseFee *big.Int // heap should always be re-sorted after baseFee is changed
- list []*types.Transaction
+ list []*types.Transaction
+
+ // Celo specific
+ ratesAndFees *exchange.RatesAndFees // current exchange rates and basefees
+ // heap should always be re-sorted after ratesAndFees is changed
}
func (h *priceHeap) Len() int { return len(h.list) }
@@ -508,18 +519,7 @@ }
}
func (h *priceHeap) cmp(a, b *types.Transaction) int {
- if h.baseFee != nil {
- // Compare effective tips if baseFee is specified
- if c := a.EffectiveGasTipCmp(b, h.baseFee); c != 0 {
- return c
- }
- }
- // Compare fee caps if baseFee is not specified or effective tips are equal
- if c := a.GasFeeCapCmp(b); c != 0 {
- return c
- }
- // Compare tips if effective tips and fee caps are equal
- return a.GasTipCapCmp(b)
+ return types.CompareWithRates(a, b, h.ratesAndFees)
}
func (h *priceHeap) Push(x interface{}) {
@@ -693,9 +693,10 @@ heap.Init(&l.floating)
reheapTimer.Update(time.Since(start))
}
-// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
+// SetBaseFeeAndRates updates the base fee and triggers a re-heap. Note that Removed is not
// necessary to call right before SetBaseFee when processing a new block.
-func (l *pricedList) SetBaseFee(baseFee *big.Int) {
- l.urgent.baseFee = baseFee
+func (l *pricedList) SetBaseFeeAndRates(baseFee *big.Int, rates common.ExchangeRates) {
+ l.urgent.ratesAndFees = exchange.NewRatesAndFees(rates, baseFee)
+ l.floating.ratesAndFees = exchange.NewRatesAndFees(rates, nil)
l.Reheap()
}
diff --git op-geth/core/txpool/legacypool/list_test.go Celo/core/txpool/legacypool/list_test.go
index 9f341a7f2026a9ef79944f542fbde08d558c21e2..55537e5965f1b33e905d4c6e933e8d500342ab31 100644
--- op-geth/core/txpool/legacypool/list_test.go
+++ Celo/core/txpool/legacypool/list_test.go
@@ -24,7 +24,6 @@
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/holiman/uint256"
)
// Tests that transactions can be added to strict lists and list contents and
@@ -40,7 +39,7 @@ }
// Insert the transactions in a random order
list := newList(true)
for _, v := range rand.Perm(len(txs)) {
- list.Add(txs[v], DefaultConfig.PriceBump, nil)
+ list.Add(txs[v], DefaultConfig.PriceBump, nil, nil)
}
// Verify internal state
if len(list.txs.items) != len(txs) {
@@ -63,8 +62,9 @@ value := big.NewInt(100)
gasprice, _ := new(big.Int).SetString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0)
gaslimit := uint64(i)
tx, _ := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, value, gaslimit, gasprice, nil), types.HomesteadSigner{}, key)
- t.Logf("cost: %x bitlen: %d\n", tx.Cost(), tx.Cost().BitLen())
- list.Add(tx, DefaultConfig.PriceBump, nil)
+ costFeeCurrency, costNative := tx.Cost()
+ t.Logf("cost: %x %x\n", costFeeCurrency, costNative)
+ list.Add(tx, DefaultConfig.PriceBump, nil, nil)
}
}
@@ -77,13 +77,11 @@ for i := 0; i < len(txs); i++ {
txs[i] = transaction(uint64(i), 0, key)
}
// Insert the transactions in a random order
- priceLimit := uint256.NewInt(DefaultConfig.PriceLimit)
b.ResetTimer()
for i := 0; i < b.N; i++ {
list := newList(true)
for _, v := range rand.Perm(len(txs)) {
- list.Add(txs[v], DefaultConfig.PriceBump, nil)
- list.Filter(priceLimit, DefaultConfig.PriceBump)
+ list.Add(txs[v], DefaultConfig.PriceBump, nil, nil)
}
}
}
@@ -102,7 +100,7 @@ for i := 0; i < b.N; i++ {
list := newList(true)
// Insert the transactions in a random order
for _, v := range rand.Perm(len(txs)) {
- list.Add(txs[v], DefaultConfig.PriceBump, nil)
+ list.Add(txs[v], DefaultConfig.PriceBump, nil, nil)
}
b.StartTimer()
list.Cap(list.Len() - 1)
diff --git op-geth/core/txpool/subpool.go Celo/core/txpool/subpool.go
index 4e2a0133f33fb592666404afbec7a9898125c7d5..aa02faaa8dcc71c379b56d30dd5881d41b4b3755 100644
--- op-geth/core/txpool/subpool.go
+++ Celo/core/txpool/subpool.go
@@ -43,6 +43,9 @@ Gas uint64 // Amount of gas required by the transaction
BlobGas uint64 // Amount of blob gas required by the transaction
DABytes *big.Int // Amount of data availability bytes this transaction may require if this is a rollup
+
+ // Celo
+ FeeCurrency *common.Address
}
// Resolve retrieves the full transaction belonging to a lazy handle if it is still
diff --git op-geth/core/txpool/validation.go Celo/core/txpool/validation.go
index 17de989ad309ccd58a547d7538d9df410a6d4e31..9e49697a95a3b59831234e523a0dd079f28394c8 100644
--- op-geth/core/txpool/validation.go
+++ Celo/core/txpool/validation.go
@@ -23,6 +23,7 @@ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@@ -74,7 +75,8 @@ // (balance, nonce, etc).
//
// This check is public to allow different transaction pools to check the basic
// rules without duplicating code and running the risk of missed updates.
-func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *ValidationOptions) error {
+// ONLY TO BE CALLED FROM "CeloValidateTransaction"
+func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *CeloValidationOptions, currencyCtx common.FeeCurrencyContext) error {
// No unauthenticated deposits allowed in the transaction pool.
// This is for spam protection, not consensus,
// as the external engine-API user authenticates deposits.
@@ -85,7 +87,7 @@ if opts.Config.IsOptimism() && tx.Type() == types.BlobTxType {
return core.ErrTxTypeNotSupported
}
// Ensure transactions not implemented by the calling pool are rejected
- if opts.Accept&(1<<tx.Type()) == 0 {
+ if !opts.Accepts(tx.Type()) {
return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type())
}
// Before performing any expensive validations, sanity check that the tx is
@@ -133,7 +135,16 @@ return ErrInvalidSender
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
- intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time))
+ intrGas, err := core.IntrinsicGas(
+ tx.Data(),
+ tx.AccessList(),
+ tx.To() == nil,
+ true,
+ opts.Config.IsIstanbul(head.Number),
+ opts.Config.IsShanghai(head.Number, head.Time),
+ tx.FeeCurrency(),
+ currencyCtx.IntrinsicGasCosts,
+ )
if err != nil {
return err
}
@@ -141,7 +152,11 @@ if tx.Gas() < intrGas {
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
}
// Ensure the gasprice is high enough to cover the requirement of the calling pool
- if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
+ minTip, err := exchange.ConvertCeloToCurrency(currencyCtx.ExchangeRates, tx.FeeCurrency(), opts.MinTip)
+ if err != nil {
+ return err
+ }
+ if tx.GasTipCapIntCmp(minTip) < 0 {
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip)
}
if tx.Type() == types.BlobTxType {
@@ -202,7 +217,7 @@
// ValidationOptionsWithState define certain differences between stateful transaction
// validation across the different pools without having to duplicate those checks.
type ValidationOptionsWithState struct {
- State *state.StateDB // State database to check nonces and balances against
+ State *state.StateDB // State database to check nonces
// FirstNonceGap is an optional callback to retrieve the first nonce gap in
// the list of pooled transactions of a specific account. If this method is
@@ -217,14 +232,27 @@ UsedAndLeftSlots func(addr common.Address) (int, int)
// ExistingExpenditure is a mandatory callback to retrieve the cumulative
// cost of the already pooled transactions to check for overdrafts.
- ExistingExpenditure func(addr common.Address) *big.Int
+ // Returns (feeCurrencySpent, nativeSpent), where feeCurrencySpent only
+ // includes the spending for txs with the relevant feeCurrency.
+ // The feeCurrency relevant for feeCurrencySpent is fixed for every
+ // instance of ExistingExpenditure and therefore not passed in as a
+ // parameter.
+ ExistingExpenditure func(addr common.Address) (*big.Int, *big.Int)
// ExistingCost is a mandatory callback to retrieve an already pooled
// transaction's cost with the given nonce to check for overdrafts.
- ExistingCost func(addr common.Address, nonce uint64) *big.Int
+ // Returns (feeCurrencyCost, nativeCost), where feeCurrencyCost only
+ // includes the spending for txs with the relevant feeCurrency.
+ // The feeCurrency relevant for feeCurrencyCost is fixed for every
+ // instance of ExistingCost, and therefore not passed in as a
+ // parameter.
+ ExistingCost func(addr common.Address, nonce uint64) (*big.Int, *big.Int)
// L1CostFn is an optional extension, to validate L1 rollup costs of a tx
L1CostFn L1CostFunc
+
+ // ExistingBalance for a currency, to check for balance to cover transaction costs.
+ ExistingBalance func(addr common.Address, feeCurrency *common.Address) *big.Int
}
// ValidateTransactionWithState is a helper method to check whether a transaction
@@ -252,30 +280,51 @@ }
}
// Ensure the transactor has enough funds to cover the transaction costs
var (
- balance = opts.State.GetBalance(from).ToBig()
- cost = tx.Cost()
+ feeCurrencyBalance = common.Big0
+ nativeBalance = opts.ExistingBalance(from, &common.ZeroAddress)
+ feeCurrencyCost, nativeCost = tx.Cost()
)
+ if tx.FeeCurrency() != nil {
+ feeCurrencyBalance = opts.ExistingBalance(from, tx.FeeCurrency())
+ }
+ if feeCurrencyBalance == nil {
+ return fmt.Errorf("feeCurrencyBalance is nil for FeeCurrency %x", tx.FeeCurrency())
+ }
if opts.L1CostFn != nil {
if l1Cost := opts.L1CostFn(tx.RollupCostData()); l1Cost != nil { // add rollup cost
- cost = cost.Add(cost, l1Cost)
+ nativeCost = nativeCost.Add(nativeCost, l1Cost)
}
}
- if balance.Cmp(cost) < 0 {
- return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance))
+ if feeCurrencyBalance.Cmp(feeCurrencyCost) < 0 {
+ return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v, fee currency: %v", core.ErrInsufficientFunds, feeCurrencyBalance, feeCurrencyCost, new(big.Int).Sub(feeCurrencyCost, feeCurrencyBalance), tx.FeeCurrency().Hex())
+ }
+ if nativeBalance.Cmp(nativeCost) < 0 {
+ return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, nativeBalance, nativeCost, new(big.Int).Sub(nativeCost, nativeBalance))
}
// Ensure the transactor has enough funds to cover for replacements or nonce
// expansions without overdrafts
- spent := opts.ExistingExpenditure(from)
- if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil {
- bump := new(big.Int).Sub(cost, prev)
- need := new(big.Int).Add(spent, bump)
- if balance.Cmp(need) < 0 {
- return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, balance, spent, bump, new(big.Int).Sub(need, balance))
+ feeCurrencySpent, nativeSpent := opts.ExistingExpenditure(from)
+ if feeCurrencyPrev, nativePrev := opts.ExistingCost(from, tx.Nonce()); feeCurrencyPrev != nil {
+ // Costs from all transactions refer to the same currency,
+ // which is ensured by ExistingCost and ExistingExpenditure.
+ feeCurrencyBump := new(big.Int).Sub(feeCurrencyCost, feeCurrencyPrev)
+ feeCurrencyNeed := new(big.Int).Add(feeCurrencySpent, feeCurrencyBump)
+ nativeBump := new(big.Int).Sub(nativeCost, nativePrev)
+ nativeNeed := new(big.Int).Add(nativeSpent, nativeBump)
+ if feeCurrencyBalance.Cmp(feeCurrencyNeed) < 0 {
+ return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v, feeCurrency %v", core.ErrInsufficientFunds, feeCurrencyBalance, feeCurrencySpent, feeCurrencyBump, new(big.Int).Sub(feeCurrencyNeed, feeCurrencyBalance), tx.FeeCurrency())
+ }
+ if nativeBalance.Cmp(nativeNeed) < 0 {
+ return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, nativeBalance, nativeSpent, nativeBump, new(big.Int).Sub(nativeNeed, nativeBalance))
}
} else {
- need := new(big.Int).Add(spent, cost)
- if balance.Cmp(need) < 0 {
- return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, balance))
+ feeCurrencyNeed := new(big.Int).Add(feeCurrencySpent, feeCurrencyCost)
+ nativeNeed := new(big.Int).Add(nativeSpent, nativeCost)
+ if feeCurrencyBalance.Cmp(feeCurrencyNeed) < 0 {
+ return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v, feeCurrency %v", core.ErrInsufficientFunds, feeCurrencyBalance, feeCurrencySpent, feeCurrencyCost, new(big.Int).Sub(feeCurrencyNeed, feeCurrencyBalance), tx.FeeCurrency())
+ }
+ if nativeBalance.Cmp(nativeNeed) < 0 {
+ return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, nativeBalance, nativeSpent, nativeCost, new(big.Int).Sub(nativeNeed, nativeBalance))
}
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
// overflow the number of permitted transactions from a single account
diff --git op-geth/core/types/celo_dynamic_fee_tx.go Celo/core/types/celo_dynamic_fee_tx.go
new file mode 100644
index 0000000000000000000000000000000000000000..f1fab1f4d8ce6e078311d6335e6fb117404cf019
--- /dev/null
+++ Celo/core/types/celo_dynamic_fee_tx.go
@@ -0,0 +1,136 @@
+// Copyright 2024 The Celo Authors
+// This file is part of the celo library.
+//
+// The celo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The celo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the celo library. If not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "bytes"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+const CeloDynamicFeeTxType = 0x7c
+
+type CeloDynamicFeeTx struct {
+ ChainID *big.Int
+ Nonce uint64
+ GasTipCap *big.Int
+ GasFeeCap *big.Int
+ Gas uint64
+ FeeCurrency *common.Address `rlp:"nil"` // nil means native currency
+ GatewayFeeRecipient *common.Address `rlp:"nil"` // nil means no gateway fee is paid
+ GatewayFee *big.Int `rlp:"nil"`
+ To *common.Address `rlp:"nil"` // nil means contract creation
+ Value *big.Int
+ Data []byte
+ AccessList AccessList
+
+ // Signature values
+ V *big.Int `json:"v" gencodec:"required"`
+ R *big.Int `json:"r" gencodec:"required"`
+ S *big.Int `json:"s" gencodec:"required"`
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *CeloDynamicFeeTx) copy() TxData {
+ cpy := &CeloDynamicFeeTx{
+ Nonce: tx.Nonce,
+ To: copyAddressPtr(tx.To),
+ Data: common.CopyBytes(tx.Data),
+ Gas: tx.Gas,
+ FeeCurrency: copyAddressPtr(tx.FeeCurrency),
+ GatewayFeeRecipient: copyAddressPtr(tx.GatewayFeeRecipient),
+ // These are copied below.
+ AccessList: make(AccessList, len(tx.AccessList)),
+ GatewayFee: new(big.Int),
+ Value: new(big.Int),
+ ChainID: new(big.Int),
+ GasTipCap: new(big.Int),
+ GasFeeCap: new(big.Int),
+ V: new(big.Int),
+ R: new(big.Int),
+ S: new(big.Int),
+ }
+ copy(cpy.AccessList, tx.AccessList)
+ if tx.Value != nil {
+ cpy.Value.Set(tx.Value)
+ }
+ if tx.ChainID != nil {
+ cpy.ChainID.Set(tx.ChainID)
+ }
+ if tx.GasTipCap != nil {
+ cpy.GasTipCap.Set(tx.GasTipCap)
+ }
+ if tx.GasFeeCap != nil {
+ cpy.GasFeeCap.Set(tx.GasFeeCap)
+ }
+ if tx.GatewayFee != nil {
+ cpy.GatewayFee.Set(tx.GatewayFee)
+ }
+ if tx.V != nil {
+ cpy.V.Set(tx.V)
+ }
+ if tx.R != nil {
+ cpy.R.Set(tx.R)
+ }
+ if tx.S != nil {
+ cpy.S.Set(tx.S)
+ }
+ return cpy
+}
+
+// accessors for innerTx.
+func (tx *CeloDynamicFeeTx) txType() byte { return CeloDynamicFeeTxType }
+func (tx *CeloDynamicFeeTx) chainID() *big.Int { return tx.ChainID }
+func (tx *CeloDynamicFeeTx) accessList() AccessList { return tx.AccessList }
+func (tx *CeloDynamicFeeTx) data() []byte { return tx.Data }
+func (tx *CeloDynamicFeeTx) gas() uint64 { return tx.Gas }
+func (tx *CeloDynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
+func (tx *CeloDynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap }
+func (tx *CeloDynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap }
+func (tx *CeloDynamicFeeTx) value() *big.Int { return tx.Value }
+func (tx *CeloDynamicFeeTx) nonce() uint64 { return tx.Nonce }
+func (tx *CeloDynamicFeeTx) to() *common.Address { return tx.To }
+func (tx *CeloDynamicFeeTx) isSystemTx() bool { return false }
+
+func (tx *CeloDynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
+ if baseFee == nil {
+ return dst.Set(tx.GasFeeCap)
+ }
+ tip := dst.Sub(tx.GasFeeCap, baseFee)
+ if tip.Cmp(tx.GasTipCap) > 0 {
+ tip.Set(tx.GasTipCap)
+ }
+ return tip.Add(tip, baseFee)
+}
+
+func (tx *CeloDynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
+ return tx.V, tx.R, tx.S
+}
+
+func (tx *CeloDynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) {
+ tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
+}
+
+func (tx *CeloDynamicFeeTx) encode(b *bytes.Buffer) error {
+ return rlp.Encode(b, tx)
+}
+
+func (tx *CeloDynamicFeeTx) decode(input []byte) error {
+ return rlp.DecodeBytes(input, tx)
+}
diff --git op-geth/core/types/celo_transaction_signing.go Celo/core/types/celo_transaction_signing.go
new file mode 100644
index 0000000000000000000000000000000000000000..823d9212d52cf6687625c889a872808777bc1fbb
--- /dev/null
+++ Celo/core/types/celo_transaction_signing.go
@@ -0,0 +1,118 @@
+// Copyright 2024 The Celo Authors
+// This file is part of the celo library.
+//
+// The celo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The celo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the celo library. If not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+var (
+ ErrDeprecatedTxType = errors.New("deprecated transaction type")
+)
+
+// celoSigner acts as an overlay signer that handles celo specific signing
+// functionality and hands off to an upstream signer for any other transaction
+// types. Unlike the signers in the go-ethereum library, the celoSigner is
+// configured with a list of forks that determine it's signing capabilities so
+// there should not be a need to create any further signers to handle celo
+// specific transaction types.
+type celoSigner struct {
+ upstreamSigner Signer
+ chainID *big.Int
+ activatedForks forks
+}
+
+// makeCeloSigner creates a new celoSigner that is configured to handle all
+// celo forks that are active at the given block time. If there are no active
+// celo forks the upstream signer will be returned.
+func makeCeloSigner(chainConfig *params.ChainConfig, blockTime uint64, upstreamSigner Signer) Signer {
+ s := &celoSigner{
+ chainID: chainConfig.ChainID,
+ upstreamSigner: upstreamSigner,
+ activatedForks: celoForks.activeForks(blockTime, chainConfig),
+ }
+
+ // If there are no active celo forks, return the upstream signer
+ if len(s.activatedForks) == 0 {
+ return upstreamSigner
+ }
+ return s
+}
+
+// latestCeloSigner creates a new celoSigner that is configured to handle all
+// celo forks for non celo transaction types it will delegate to the given
+// upstream signer.
+func latestCeloSigner(chainID *big.Int, upstreamSigner Signer) Signer {
+ return &celoSigner{
+ chainID: chainID,
+ upstreamSigner: upstreamSigner,
+ activatedForks: celoForks,
+ }
+}
+
+// Sender implements Signer.
+func (c *celoSigner) Sender(tx *Transaction) (common.Address, error) {
+ if funcs := c.findTxFuncs(tx); funcs != nil {
+ return funcs.sender(tx, funcs.hash, c.ChainID())
+ }
+ return c.upstreamSigner.Sender(tx)
+}
+
+// SignatureValues implements Signer.
+func (c *celoSigner) SignatureValues(tx *Transaction, sig []byte) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ if funcs := c.findTxFuncs(tx); funcs != nil {
+ return funcs.signatureValues(tx, sig, c.ChainID())
+ }
+ return c.upstreamSigner.SignatureValues(tx, sig)
+}
+
+// Hash implements Signer.
+func (c *celoSigner) Hash(tx *Transaction) common.Hash {
+ if funcs := c.findTxFuncs(tx); funcs != nil {
+ return funcs.hash(tx, c.ChainID())
+ }
+ return c.upstreamSigner.Hash(tx)
+}
+
+// findTxFuncs returns the txFuncs for the given tx if it is supported by one
+// of the active forks. Note that this mechanism can be used to deprecate
+// support for tx types by having forks return deprecatedTxFuncs for a tx type.
+func (c *celoSigner) findTxFuncs(tx *Transaction) *txFuncs {
+ return c.activatedForks.findTxFuncs(tx)
+}
+
+// ChainID implements Signer.
+func (c *celoSigner) ChainID() *big.Int {
+ return c.chainID
+}
+
+// Equal implements Signer.
+func (c *celoSigner) Equal(s Signer) bool {
+ // Normally signers just check to see if the chainID and type are equal,
+ // because their logic is hardcoded to a specific fork. In our case we need
+ // to also know that the two signers have matching latest forks.
+ other, ok := s.(*celoSigner)
+ return ok && c.ChainID() == other.ChainID() && c.latestFork().equal(other.latestFork())
+}
+
+func (c *celoSigner) latestFork() fork {
+ return c.activatedForks[len(c.activatedForks)-1]
+}
diff --git op-geth/core/types/receipt.go Celo/core/types/receipt.go
index 4bc83bf988f84dbe37f3fc0a04577c2838f9363a..7b2befc199c8e276e34a6d8d30741c659c13ca0d 100644
--- op-geth/core/types/receipt.go
+++ Celo/core/types/receipt.go
@@ -92,6 +92,11 @@ L1Fee *big.Int `json:"l1Fee,omitempty"` // Present from pre-bedrock
FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` // Present from pre-bedrock to Ecotone. Nil after Ecotone
L1BaseFeeScalar *uint64 `json:"l1BaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork
L1BlobBaseFeeScalar *uint64 `json:"l1BlobBaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork
+
+ // Celo
+ // The BaseFee is stored in fee currency for fee currency txs. We need
+ // this field to calculate the EffectiveGasPrice for fee currency txs.
+ BaseFee *big.Int `json:"baseFee,omitempty"`
}
type receiptMarshaling struct {
@@ -256,6 +261,9 @@ // encodeTyped writes the canonical encoding of a typed receipt to w.
func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error {
w.WriteByte(r.Type)
switch r.Type {
+ case CeloDynamicFeeTxV2Type:
+ withBaseFee := &celoDynamicReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.BaseFee}
+ return rlp.Encode(w, withBaseFee)
case DepositTxType:
withNonce := &depositReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.DepositNonce, r.DepositReceiptVersion}
return rlp.Encode(w, withNonce)
@@ -329,7 +337,7 @@ if len(b) <= 1 {
return errShortTypedReceipt
}
switch b[0] {
- case DynamicFeeTxType, AccessListTxType, BlobTxType:
+ case DynamicFeeTxType, AccessListTxType, BlobTxType, CeloDynamicFeeTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
@@ -337,6 +345,15 @@ return err
}
r.Type = b[0]
return r.setFromRLP(data)
+ case CeloDynamicFeeTxV2Type:
+ var data celoDynamicReceiptRLP
+ err := rlp.DecodeBytes(b[1:], &data)
+ if err != nil {
+ return err
+ }
+ r.Type = b[0]
+ r.BaseFee = data.BaseFee
+ return r.setFromRLP(receiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs})
case DepositTxType:
var data depositReceiptRLP
err := rlp.DecodeBytes(b[1:], &data)
@@ -401,6 +418,11 @@ // into an RLP stream.
func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
outerList := w.List()
+ if r.Type == CeloDynamicFeeTxV2Type {
+ // Mark receipt as CeloDynamicFee receipt by starting with an empty list
+ listIndex := w.List()
+ w.ListEnd(listIndex)
+ }
w.WriteBytes((*Receipt)(r).statusEncoding())
w.WriteUint64(r.CumulativeGasUsed)
logList := w.List()
@@ -416,6 +438,9 @@ if r.DepositReceiptVersion != nil {
w.WriteUint64(*r.DepositReceiptVersion)
}
}
+ if r.Type == CeloDynamicFeeTxV2Type && r.BaseFee != nil {
+ w.WriteBigInt(r.BaseFee)
+ }
w.ListEnd(outerList)
return w.Flush()
}
@@ -429,6 +454,9 @@ if err != nil {
return err
}
// First try to decode the latest receipt database format, try the pre-bedrock Optimism legacy format otherwise.
+ if IsCeloDynamicFeeReceipt(blob) {
+ return decodeStoredCeloDynamicFeeReceiptRLP(r, blob)
+ }
if err := decodeStoredReceiptRLP(r, blob); err == nil {
return nil
}
@@ -502,8 +530,11 @@ return
}
w.WriteByte(r.Type)
switch r.Type {
- case AccessListTxType, DynamicFeeTxType, BlobTxType:
+ case AccessListTxType, DynamicFeeTxType, BlobTxType, CeloDynamicFeeTxType:
rlp.Encode(w, data)
+ case CeloDynamicFeeTxV2Type:
+ celoDynamicData := &celoDynamicReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.BaseFee}
+ rlp.Encode(w, celoDynamicData)
case DepositTxType:
if r.DepositReceiptVersion != nil {
// post-canyon receipt hash computation update
@@ -525,14 +556,39 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error {
signer := MakeSigner(config, new(big.Int).SetUint64(number), time)
logIndex := uint(0)
- if len(txs) != len(rs) {
+
+ // If rs are one longer than txs it indicates the presence of a celo block receipt.
+ if len(txs) != len(rs) && len(txs)+1 != len(rs) {
return errors.New("transaction and receipt count mismatch")
}
- for i := 0; i < len(rs); i++ {
+ for i := 0; i < len(txs); i++ {
// The transaction type and hash can be retrieved from the transaction itself
rs[i].Type = txs[i].Type()
rs[i].TxHash = txs[i].Hash()
- rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee)
+
+ switch rs[i].Type {
+ case LegacyTxType, AccessListTxType:
+ // These are the non dynamic tx types so we can simply set effective gas price to gas price.
+ rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee)
+ default:
+ // Pre-gingerbred the base fee was stored in state, but we don't try to recover it here, since A) we don't
+ // have access to the objects required to get the state and B) retrieving the base fee is quite code heavy
+ // and we don't want to bring that code across from the celo L1 to op-geth. In the celo L1 we would return a
+ // nil base fee if the state was not available, so that is what we do here.
+ //
+ // We also check for the London hardfork here, in order to not break tests from upstream that have not
+ // configured the gingerbread block, since the london hardfork introduced dynamic fee transactions.
+ if config.IsGingerbread(new(big.Int).SetUint64(number)) || config.IsLondon(new(big.Int).SetUint64(number)) {
+ // The post transition CeloDynamicFeeV2Txs set the baseFee in the receipt, so if we have it use it.
+ // Otherwise we can set the effectiveGasPrice only if the transaction does not specify a fee currency,
+ // since we would need state to discover the true base fee.
+ if rs[i].BaseFee != nil {
+ rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), rs[i].BaseFee)
+ } else if txs[i].FeeCurrency() == nil || txs[i].Type() == CeloDenominatedTxType {
+ rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee)
+ }
+ }
+ }
// EIP-4844 blob transaction fields
if txs[i].Type() == BlobTxType {
@@ -575,6 +631,20 @@ rs[i].Logs[j].Index = logIndex
logIndex++
}
}
+
+ // This is a celo block receipt, which uses the block hash in place of the tx hash.
+ if len(txs)+1 == len(rs) {
+ j := len(txs)
+ for k := 0; k < len(rs[j].Logs); k++ {
+ rs[j].Logs[k].BlockNumber = number
+ rs[j].Logs[k].BlockHash = hash
+ rs[j].Logs[k].TxHash = hash
+ rs[j].Logs[k].TxIndex = uint(j)
+ rs[j].Logs[k].Index = logIndex
+ logIndex++
+ }
+ }
+
if config.Optimism != nil && len(txs) >= 2 && config.IsBedrock(new(big.Int).SetUint64(number)) { // need at least an info tx and a non-info tx
gasParams, err := extractL1GasParams(config, time, txs[0].Data())
if err != nil {
diff --git op-geth/core/types/receipt_test.go Celo/core/types/receipt_test.go
index 76599fdd323980c098bca80d62393ec0bd945dae..49d23ace0c0c56888511c71942d85a86ec2c4e99 100644
--- op-geth/core/types/receipt_test.go
+++ Celo/core/types/receipt_test.go
@@ -99,6 +99,24 @@ },
},
Type: DynamicFeeTxType,
}
+ celoDynamicFeeReceipt = &Receipt{
+ Status: ReceiptStatusFailed,
+ CumulativeGasUsed: 1,
+ Logs: []*Log{
+ {
+ Address: common.BytesToAddress([]byte{0x11}),
+ Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
+ Data: []byte{0x01, 0x00, 0xff},
+ },
+ {
+ Address: common.BytesToAddress([]byte{0x01, 0x11}),
+ Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
+ Data: []byte{0x01, 0x00, 0xff},
+ },
+ },
+ BaseFee: new(big.Int).SetUint64(1),
+ Type: CeloDynamicFeeTxV2Type,
+ }
depositReceiptNoNonce = &Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
@@ -624,6 +642,24 @@ eip1559Want := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
if !bytes.Equal(have, eip1559Want) {
t.Errorf("encoded RLP mismatch, got %x want %x", have, eip1559Want)
}
+
+ // Celo Dynamic Fee Receipt
+ buf.Reset()
+ celoDynamicFeeReceipt.Bloom = CreateBloom(Receipts{celoDynamicFeeReceipt})
+ have, err = celoDynamicFeeReceipt.MarshalBinary()
+ if err != nil {
+ t.Fatalf("marshal binary error: %v", err)
+ }
+ celoDynamicFeeReceipts := Receipts{celoDynamicFeeReceipt}
+ celoDynamicFeeReceipts.EncodeIndex(0, buf)
+ haveEncodeIndex = buf.Bytes()
+ if !bytes.Equal(have, haveEncodeIndex) {
+ t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex)
+ }
+ celoDynamicFeeWant := common.FromHex("7bf901c68001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff01")
+ if !bytes.Equal(have, celoDynamicFeeWant) {
+ t.Errorf("encoded RLP mismatch, got %x want %x", have, celoDynamicFeeWant)
+ }
}
func TestReceiptUnmarshalBinary(t *testing.T) {
@@ -1002,6 +1038,7 @@ }{
{name: "Legacy", rcpt: legacyReceipt},
{name: "AccessList", rcpt: accessListReceipt},
{name: "EIP1559", rcpt: eip1559Receipt},
+ {name: "CeloDynamicFee", rcpt: celoDynamicFeeReceipt},
{name: "DepositNoNonce", rcpt: depositReceiptNoNonce},
{name: "DepositWithNonce", rcpt: depositReceiptWithNonce},
{name: "DepositWithNonceAndVersion", rcpt: depositReceiptWithNonceAndVersion},
@@ -1038,6 +1075,7 @@ }{
{name: "Legacy", rcpt: legacyReceipt},
{name: "AccessList", rcpt: accessListReceipt},
{name: "EIP1559", rcpt: eip1559Receipt},
+ {name: "CeloDynamicFee", rcpt: celoDynamicFeeReceipt},
{name: "DepositNoNonce", rcpt: depositReceiptNoNonce},
{name: "DepositWithNonce", rcpt: depositReceiptWithNonce},
{name: "DepositWithNonceAndVersion", rcpt: depositReceiptWithNonceAndVersion},
@@ -1056,6 +1094,9 @@ require.Equal(t, test.rcpt.CumulativeGasUsed, d.CumulativeGasUsed)
require.Equal(t, test.rcpt.Logs, d.Logs)
require.Equal(t, test.rcpt.DepositNonce, d.DepositNonce)
require.Equal(t, test.rcpt.DepositReceiptVersion, d.DepositReceiptVersion)
+ if test.rcpt.Type == CeloDynamicFeeTxV2Type {
+ require.Equal(t, test.rcpt.EffectiveGasPrice, d.EffectiveGasPrice)
+ }
})
}
}
diff --git op-geth/core/types/transaction.go Celo/core/types/transaction.go
index 3c94261061476ac453aaf252e49e0b66d8635cd9..b5ee77df6d141f61cac05eb22bb4c5a76d236992 100644
--- op-geth/core/types/transaction.go
+++ Celo/core/types/transaction.go
@@ -209,6 +209,11 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) <= 1 {
return nil, errShortTypedTx
}
+
+ if inner, isCelo, err := celoDecodeTyped(b); isCelo {
+ return inner, err
+ }
+
var inner TxData
switch b[0] {
case AccessListTxType:
@@ -372,14 +377,29 @@ func (tx *Transaction) IsSystemTx() bool {
return tx.inner.isSystemTx()
}
-// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value.
-func (tx *Transaction) Cost() *big.Int {
+// Cost returns both components of the tx costs:
+// - cost in feeCurrency: (gas * gasPrice) + (blobGas * blobGasPrice)
+// - native token cost: value sent to target contract
+// For non-feeCurrency transactions, the first value is zero and the second is the total cost.
+func (tx *Transaction) Cost() (*big.Int, *big.Int) {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
if tx.Type() == BlobTxType {
total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas())))
}
- total.Add(total, tx.Value())
- return total
+ if tx.FeeCurrency() == nil {
+ nativeCost := total.Add(total, tx.Value())
+ return new(big.Int), nativeCost
+ } else {
+ // Will need to be updated for CIP-66
+ nativeCost := tx.Value()
+ return total, nativeCost
+ }
+}
+
+// Returns the native token component of the transaction cost.
+func (tx *Transaction) NativeCost() *big.Int {
+ _, nativeCost := tx.Cost()
+ return nativeCost
}
// RollupCostData caches the information needed to efficiently compute the data availability fee
diff --git op-geth/core/types/transaction_marshalling.go Celo/core/types/transaction_marshalling.go
index a07d65414c3846e49412a3083371e93b843d2553..a9dd124ceb500e033c40b9f374f8c8c5ba41e073 100644
--- op-geth/core/types/transaction_marshalling.go
+++ Celo/core/types/transaction_marshalling.go
@@ -63,6 +63,13 @@ Proofs []kzg4844.Proof `json:"proofs,omitempty"`
// Only used for encoding:
Hash common.Hash `json:"hash"`
+
+ // Celo specific fields
+ FeeCurrency *common.Address `json:"feeCurrency,omitempty"` // nil means native currency
+ MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"`
+ EthCompatible *bool `json:"ethCompatible,omitempty"`
+ GatewayFee *hexutil.Big `json:"gatewayFee,omitempty"`
+ GatewayFeeRecipient *common.Address `json:"gatewayFeeRecipient,omitempty"`
}
// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons,
@@ -87,6 +94,9 @@ }
// MarshalJSON marshals as JSON with a hash.
func (tx *Transaction) MarshalJSON() ([]byte, error) {
+ if marshalled, isCelo, err := celoTransactionMarshal(tx); isCelo {
+ return marshalled, err
+ }
var enc txJSON
// These are set for all tx types.
enc.Hash = tx.Hash()
@@ -202,6 +212,12 @@ }
// Decode / verify fields according to transaction type.
var inner TxData
+
+ if isCelo, err := celoTransactionUnmarshal(dec, &inner); isCelo {
+ tx.setDecoded(inner, 0)
+ return err
+ }
+
switch dec.Type {
case LegacyTxType:
var itx LegacyTx
diff --git op-geth/core/types/transaction_signing.go Celo/core/types/transaction_signing.go
index 1238c9a6c38d7a5485f4ccf2b8c9b95ea7e5b48a..2bbb92afd758217d979fe0eb278ac70653c66f6c 100644
--- op-geth/core/types/transaction_signing.go
+++ Celo/core/types/transaction_signing.go
@@ -53,6 +53,10 @@ signer = HomesteadSigner{}
default:
signer = FrontierSigner{}
}
+
+ // Apply the celo overlay signer, if no celo forks have been enabled then the given signer is returned.
+ signer = makeCeloSigner(config, blockTime, signer)
+
return signer
}
@@ -67,6 +71,13 @@ func LatestSigner(config *params.ChainConfig) Signer {
var signer Signer
if config.ChainID != nil {
switch {
+ case config.Cel2Time != nil:
+ if config.CancunTime != nil {
+ // This branch is only used in testing
+ return latestCeloSigner(config.ChainID, NewCancunSigner(config.ChainID))
+ } else {
+ return latestCeloSigner(config.ChainID, NewLondonSigner(config.ChainID))
+ }
case config.CancunTime != nil && !config.IsOptimism():
signer = NewCancunSigner(config.ChainID)
case config.LondonBlock != nil:
@@ -94,7 +105,7 @@ // If you have a ChainConfig and know the current block number, use MakeSigner instead.
func LatestSignerForChainID(chainID *big.Int) Signer {
var signer Signer
if chainID != nil {
- signer = NewCancunSigner(chainID)
+ signer = latestCeloSigner(chainID, NewCancunSigner(chainID))
} else {
signer = HomesteadSigner{}
}
diff --git op-geth/core/types/tx_legacy.go Celo/core/types/tx_legacy.go
index bcb65c39349a3a1bef78948af48a30a1e14c71ba..782c3934c74eff4cbd26c290f72073be92c0d528 100644
--- op-geth/core/types/tx_legacy.go
+++ Celo/core/types/tx_legacy.go
@@ -25,13 +25,25 @@ )
// LegacyTx is the transaction data of the original Ethereum transactions.
type LegacyTx struct {
- Nonce uint64 // nonce of sender account
- GasPrice *big.Int // wei per gas
- Gas uint64 // gas limit
- To *common.Address `rlp:"nil"` // nil means contract creation
- Value *big.Int // wei amount
- Data []byte // contract invocation input data
- V, R, S *big.Int // signature values
+ Nonce uint64 // nonce of sender account
+ GasPrice *big.Int // wei per gas
+ Gas uint64 // gas limit
+
+ // Celo-specific fields
+ FeeCurrency *common.Address // nil means native currency
+ GatewayFeeRecipient *common.Address // nil means no gateway fee is paid
+ GatewayFee *big.Int
+
+ To *common.Address `rlp:"nil"` // nil means contract creation
+ Value *big.Int // wei amount
+ Data []byte // contract invocation input data
+ V, R, S *big.Int // signature values
+
+ // This is only used when marshaling to JSON.
+ Hash *common.Hash `rlp:"-"`
+
+ // Whether this is a celo legacy transaction (i.e. with FeeCurrency, GatewayFeeRecipient and GatewayFee)
+ CeloLegacy bool `rlp:"-"`
}
// NewTransaction creates an unsigned legacy transaction.
@@ -72,6 +84,12 @@ GasPrice: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
+
+ // Celo specific fields
+ FeeCurrency: copyAddressPtr(tx.FeeCurrency),
+ GatewayFeeRecipient: copyAddressPtr(tx.GatewayFeeRecipient),
+ GatewayFee: copyBigInt(tx.GatewayFee),
+ CeloLegacy: tx.CeloLegacy,
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
Celo contracts
+2494
-0
Contract bindings are necessary for token duality and gas currencies. The corresponding contracts are included to generate the bindings and test these features.
diff --git op-geth/contracts/celo/README.md Celo/contracts/celo/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3a0735f1fecc93d0c9e1f5932a755c022d139ad2
--- /dev/null
+++ Celo/contracts/celo/README.md
@@ -0,0 +1,18 @@
+# Celo contracts bytecode and ABI
+
+## Why contracts in this repo?
+
+The contracts bytecode is used to generate the genesis block in `geth --dev`
+mode while the ABI is used to generate the contract bindings in `abigen`. The
+bindings are necessary to access the FeeCurrencyDirectory and fee currencies.
+
+## How to update to newer contracts
+
+To compile contracts in the optimism repo and extract their ABI and bin-runtime
+for relevant contracts into this repo, run `compiled/update.sh`. If your
+optimism repo is not at `~/optimism`, set the `CELO_OPTIMISM_REPO` env variable
+accordingly. The same is true for `~/celo-monorepo` and the `CELO_MONOREPO` env var.
+
+## How to rebuild ABI wrappers
+
+Use `go generate`, e.g. as `go generate ./contracts/celo/celo.go`.
diff --git op-geth/contracts/celo/abigen/FeeCurrency.go Celo/contracts/celo/abigen/FeeCurrency.go
new file mode 100644
index 0000000000000000000000000000000000000000..64b51d54185afdb0838eda5b80a8c9728b3e56e3
--- /dev/null
+++ Celo/contracts/celo/abigen/FeeCurrency.go
@@ -0,0 +1,822 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package abigen
+
+import (
+ "errors"
+ "math/big"
+ "strings"
+
+ ethereum "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var (
+ _ = errors.New
+ _ = big.NewInt
+ _ = strings.NewReader
+ _ = ethereum.NotFound
+ _ = bind.Bind
+ _ = common.Big1
+ _ = types.BloomLookup
+ _ = event.NewSubscription
+ _ = abi.ConvertType
+)
+
+// FeeCurrencyMetaData contains all meta data concerning the FeeCurrency contract.
+var FeeCurrencyMetaData = &bind.MetaData{
+ ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"name_\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"symbol_\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"creditGasFees\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feeRecipient\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"communityFund\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"refund\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"tipTxFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"baseTxFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"debitGasFees\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"decreaseAllowance\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"subtractedValue\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"increaseAllowance\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"addedValue\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]",
+}
+
+// FeeCurrencyABI is the input ABI used to generate the binding from.
+// Deprecated: Use FeeCurrencyMetaData.ABI instead.
+var FeeCurrencyABI = FeeCurrencyMetaData.ABI
+
+// FeeCurrency is an auto generated Go binding around an Ethereum contract.
+type FeeCurrency struct {
+ FeeCurrencyCaller // Read-only binding to the contract
+ FeeCurrencyTransactor // Write-only binding to the contract
+ FeeCurrencyFilterer // Log filterer for contract events
+}
+
+// FeeCurrencyCaller is an auto generated read-only Go binding around an Ethereum contract.
+type FeeCurrencyCaller struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// FeeCurrencyTransactor is an auto generated write-only Go binding around an Ethereum contract.
+type FeeCurrencyTransactor struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// FeeCurrencyFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
+type FeeCurrencyFilterer struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// FeeCurrencySession is an auto generated Go binding around an Ethereum contract,
+// with pre-set call and transact options.
+type FeeCurrencySession struct {
+ Contract *FeeCurrency // Generic contract binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// FeeCurrencyCallerSession is an auto generated read-only Go binding around an Ethereum contract,
+// with pre-set call options.
+type FeeCurrencyCallerSession struct {
+ Contract *FeeCurrencyCaller // Generic contract caller binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+}
+
+// FeeCurrencyTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
+// with pre-set transact options.
+type FeeCurrencyTransactorSession struct {
+ Contract *FeeCurrencyTransactor // Generic contract transactor binding to set the session for
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// FeeCurrencyRaw is an auto generated low-level Go binding around an Ethereum contract.
+type FeeCurrencyRaw struct {
+ Contract *FeeCurrency // Generic contract binding to access the raw methods on
+}
+
+// FeeCurrencyCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
+type FeeCurrencyCallerRaw struct {
+ Contract *FeeCurrencyCaller // Generic read-only contract binding to access the raw methods on
+}
+
+// FeeCurrencyTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
+type FeeCurrencyTransactorRaw struct {
+ Contract *FeeCurrencyTransactor // Generic write-only contract binding to access the raw methods on
+}
+
+// NewFeeCurrency creates a new instance of FeeCurrency, bound to a specific deployed contract.
+func NewFeeCurrency(address common.Address, backend bind.ContractBackend) (*FeeCurrency, error) {
+ contract, err := bindFeeCurrency(address, backend, backend, backend)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrency{FeeCurrencyCaller: FeeCurrencyCaller{contract: contract}, FeeCurrencyTransactor: FeeCurrencyTransactor{contract: contract}, FeeCurrencyFilterer: FeeCurrencyFilterer{contract: contract}}, nil
+}
+
+// NewFeeCurrencyCaller creates a new read-only instance of FeeCurrency, bound to a specific deployed contract.
+func NewFeeCurrencyCaller(address common.Address, caller bind.ContractCaller) (*FeeCurrencyCaller, error) {
+ contract, err := bindFeeCurrency(address, caller, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyCaller{contract: contract}, nil
+}
+
+// NewFeeCurrencyTransactor creates a new write-only instance of FeeCurrency, bound to a specific deployed contract.
+func NewFeeCurrencyTransactor(address common.Address, transactor bind.ContractTransactor) (*FeeCurrencyTransactor, error) {
+ contract, err := bindFeeCurrency(address, nil, transactor, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyTransactor{contract: contract}, nil
+}
+
+// NewFeeCurrencyFilterer creates a new log filterer instance of FeeCurrency, bound to a specific deployed contract.
+func NewFeeCurrencyFilterer(address common.Address, filterer bind.ContractFilterer) (*FeeCurrencyFilterer, error) {
+ contract, err := bindFeeCurrency(address, nil, nil, filterer)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyFilterer{contract: contract}, nil
+}
+
+// bindFeeCurrency binds a generic wrapper to an already deployed contract.
+func bindFeeCurrency(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+ parsed, err := FeeCurrencyMetaData.GetAbi()
+ if err != nil {
+ return nil, err
+ }
+ return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_FeeCurrency *FeeCurrencyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _FeeCurrency.Contract.FeeCurrencyCaller.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_FeeCurrency *FeeCurrencyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.FeeCurrencyTransactor.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_FeeCurrency *FeeCurrencyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.FeeCurrencyTransactor.contract.Transact(opts, method, params...)
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_FeeCurrency *FeeCurrencyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _FeeCurrency.Contract.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_FeeCurrency *FeeCurrencyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_FeeCurrency *FeeCurrencyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.contract.Transact(opts, method, params...)
+}
+
+// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e.
+//
+// Solidity: function allowance(address owner, address spender) view returns(uint256)
+func (_FeeCurrency *FeeCurrencyCaller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) {
+ var out []interface{}
+ err := _FeeCurrency.contract.Call(opts, &out, "allowance", owner, spender)
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e.
+//
+// Solidity: function allowance(address owner, address spender) view returns(uint256)
+func (_FeeCurrency *FeeCurrencySession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) {
+ return _FeeCurrency.Contract.Allowance(&_FeeCurrency.CallOpts, owner, spender)
+}
+
+// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e.
+//
+// Solidity: function allowance(address owner, address spender) view returns(uint256)
+func (_FeeCurrency *FeeCurrencyCallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) {
+ return _FeeCurrency.Contract.Allowance(&_FeeCurrency.CallOpts, owner, spender)
+}
+
+// BalanceOf is a free data retrieval call binding the contract method 0x70a08231.
+//
+// Solidity: function balanceOf(address account) view returns(uint256)
+func (_FeeCurrency *FeeCurrencyCaller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) {
+ var out []interface{}
+ err := _FeeCurrency.contract.Call(opts, &out, "balanceOf", account)
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// BalanceOf is a free data retrieval call binding the contract method 0x70a08231.
+//
+// Solidity: function balanceOf(address account) view returns(uint256)
+func (_FeeCurrency *FeeCurrencySession) BalanceOf(account common.Address) (*big.Int, error) {
+ return _FeeCurrency.Contract.BalanceOf(&_FeeCurrency.CallOpts, account)
+}
+
+// BalanceOf is a free data retrieval call binding the contract method 0x70a08231.
+//
+// Solidity: function balanceOf(address account) view returns(uint256)
+func (_FeeCurrency *FeeCurrencyCallerSession) BalanceOf(account common.Address) (*big.Int, error) {
+ return _FeeCurrency.Contract.BalanceOf(&_FeeCurrency.CallOpts, account)
+}
+
+// Decimals is a free data retrieval call binding the contract method 0x313ce567.
+//
+// Solidity: function decimals() view returns(uint8)
+func (_FeeCurrency *FeeCurrencyCaller) Decimals(opts *bind.CallOpts) (uint8, error) {
+ var out []interface{}
+ err := _FeeCurrency.contract.Call(opts, &out, "decimals")
+
+ if err != nil {
+ return *new(uint8), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8)
+
+ return out0, err
+
+}
+
+// Decimals is a free data retrieval call binding the contract method 0x313ce567.
+//
+// Solidity: function decimals() view returns(uint8)
+func (_FeeCurrency *FeeCurrencySession) Decimals() (uint8, error) {
+ return _FeeCurrency.Contract.Decimals(&_FeeCurrency.CallOpts)
+}
+
+// Decimals is a free data retrieval call binding the contract method 0x313ce567.
+//
+// Solidity: function decimals() view returns(uint8)
+func (_FeeCurrency *FeeCurrencyCallerSession) Decimals() (uint8, error) {
+ return _FeeCurrency.Contract.Decimals(&_FeeCurrency.CallOpts)
+}
+
+// Name is a free data retrieval call binding the contract method 0x06fdde03.
+//
+// Solidity: function name() view returns(string)
+func (_FeeCurrency *FeeCurrencyCaller) Name(opts *bind.CallOpts) (string, error) {
+ var out []interface{}
+ err := _FeeCurrency.contract.Call(opts, &out, "name")
+
+ if err != nil {
+ return *new(string), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+ return out0, err
+
+}
+
+// Name is a free data retrieval call binding the contract method 0x06fdde03.
+//
+// Solidity: function name() view returns(string)
+func (_FeeCurrency *FeeCurrencySession) Name() (string, error) {
+ return _FeeCurrency.Contract.Name(&_FeeCurrency.CallOpts)
+}
+
+// Name is a free data retrieval call binding the contract method 0x06fdde03.
+//
+// Solidity: function name() view returns(string)
+func (_FeeCurrency *FeeCurrencyCallerSession) Name() (string, error) {
+ return _FeeCurrency.Contract.Name(&_FeeCurrency.CallOpts)
+}
+
+// Symbol is a free data retrieval call binding the contract method 0x95d89b41.
+//
+// Solidity: function symbol() view returns(string)
+func (_FeeCurrency *FeeCurrencyCaller) Symbol(opts *bind.CallOpts) (string, error) {
+ var out []interface{}
+ err := _FeeCurrency.contract.Call(opts, &out, "symbol")
+
+ if err != nil {
+ return *new(string), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+ return out0, err
+
+}
+
+// Symbol is a free data retrieval call binding the contract method 0x95d89b41.
+//
+// Solidity: function symbol() view returns(string)
+func (_FeeCurrency *FeeCurrencySession) Symbol() (string, error) {
+ return _FeeCurrency.Contract.Symbol(&_FeeCurrency.CallOpts)
+}
+
+// Symbol is a free data retrieval call binding the contract method 0x95d89b41.
+//
+// Solidity: function symbol() view returns(string)
+func (_FeeCurrency *FeeCurrencyCallerSession) Symbol() (string, error) {
+ return _FeeCurrency.Contract.Symbol(&_FeeCurrency.CallOpts)
+}
+
+// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd.
+//
+// Solidity: function totalSupply() view returns(uint256)
+func (_FeeCurrency *FeeCurrencyCaller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) {
+ var out []interface{}
+ err := _FeeCurrency.contract.Call(opts, &out, "totalSupply")
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd.
+//
+// Solidity: function totalSupply() view returns(uint256)
+func (_FeeCurrency *FeeCurrencySession) TotalSupply() (*big.Int, error) {
+ return _FeeCurrency.Contract.TotalSupply(&_FeeCurrency.CallOpts)
+}
+
+// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd.
+//
+// Solidity: function totalSupply() view returns(uint256)
+func (_FeeCurrency *FeeCurrencyCallerSession) TotalSupply() (*big.Int, error) {
+ return _FeeCurrency.Contract.TotalSupply(&_FeeCurrency.CallOpts)
+}
+
+// Approve is a paid mutator transaction binding the contract method 0x095ea7b3.
+//
+// Solidity: function approve(address spender, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactor) Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "approve", spender, amount)
+}
+
+// Approve is a paid mutator transaction binding the contract method 0x095ea7b3.
+//
+// Solidity: function approve(address spender, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencySession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.Approve(&_FeeCurrency.TransactOpts, spender, amount)
+}
+
+// Approve is a paid mutator transaction binding the contract method 0x095ea7b3.
+//
+// Solidity: function approve(address spender, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactorSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.Approve(&_FeeCurrency.TransactOpts, spender, amount)
+}
+
+// CreditGasFees is a paid mutator transaction binding the contract method 0x6a30b253.
+//
+// Solidity: function creditGasFees(address from, address feeRecipient, address , address communityFund, uint256 refund, uint256 tipTxFee, uint256 , uint256 baseTxFee) returns()
+func (_FeeCurrency *FeeCurrencyTransactor) CreditGasFees(opts *bind.TransactOpts, from common.Address, feeRecipient common.Address, arg2 common.Address, communityFund common.Address, refund *big.Int, tipTxFee *big.Int, arg6 *big.Int, baseTxFee *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "creditGasFees", from, feeRecipient, arg2, communityFund, refund, tipTxFee, arg6, baseTxFee)
+}
+
+// CreditGasFees is a paid mutator transaction binding the contract method 0x6a30b253.
+//
+// Solidity: function creditGasFees(address from, address feeRecipient, address , address communityFund, uint256 refund, uint256 tipTxFee, uint256 , uint256 baseTxFee) returns()
+func (_FeeCurrency *FeeCurrencySession) CreditGasFees(from common.Address, feeRecipient common.Address, arg2 common.Address, communityFund common.Address, refund *big.Int, tipTxFee *big.Int, arg6 *big.Int, baseTxFee *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.CreditGasFees(&_FeeCurrency.TransactOpts, from, feeRecipient, arg2, communityFund, refund, tipTxFee, arg6, baseTxFee)
+}
+
+// CreditGasFees is a paid mutator transaction binding the contract method 0x6a30b253.
+//
+// Solidity: function creditGasFees(address from, address feeRecipient, address , address communityFund, uint256 refund, uint256 tipTxFee, uint256 , uint256 baseTxFee) returns()
+func (_FeeCurrency *FeeCurrencyTransactorSession) CreditGasFees(from common.Address, feeRecipient common.Address, arg2 common.Address, communityFund common.Address, refund *big.Int, tipTxFee *big.Int, arg6 *big.Int, baseTxFee *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.CreditGasFees(&_FeeCurrency.TransactOpts, from, feeRecipient, arg2, communityFund, refund, tipTxFee, arg6, baseTxFee)
+}
+
+// DebitGasFees is a paid mutator transaction binding the contract method 0x58cf9672.
+//
+// Solidity: function debitGasFees(address from, uint256 value) returns()
+func (_FeeCurrency *FeeCurrencyTransactor) DebitGasFees(opts *bind.TransactOpts, from common.Address, value *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "debitGasFees", from, value)
+}
+
+// DebitGasFees is a paid mutator transaction binding the contract method 0x58cf9672.
+//
+// Solidity: function debitGasFees(address from, uint256 value) returns()
+func (_FeeCurrency *FeeCurrencySession) DebitGasFees(from common.Address, value *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.DebitGasFees(&_FeeCurrency.TransactOpts, from, value)
+}
+
+// DebitGasFees is a paid mutator transaction binding the contract method 0x58cf9672.
+//
+// Solidity: function debitGasFees(address from, uint256 value) returns()
+func (_FeeCurrency *FeeCurrencyTransactorSession) DebitGasFees(from common.Address, value *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.DebitGasFees(&_FeeCurrency.TransactOpts, from, value)
+}
+
+// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7.
+//
+// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactor) DecreaseAllowance(opts *bind.TransactOpts, spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "decreaseAllowance", spender, subtractedValue)
+}
+
+// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7.
+//
+// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool)
+func (_FeeCurrency *FeeCurrencySession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.DecreaseAllowance(&_FeeCurrency.TransactOpts, spender, subtractedValue)
+}
+
+// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7.
+//
+// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactorSession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.DecreaseAllowance(&_FeeCurrency.TransactOpts, spender, subtractedValue)
+}
+
+// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351.
+//
+// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactor) IncreaseAllowance(opts *bind.TransactOpts, spender common.Address, addedValue *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "increaseAllowance", spender, addedValue)
+}
+
+// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351.
+//
+// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool)
+func (_FeeCurrency *FeeCurrencySession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.IncreaseAllowance(&_FeeCurrency.TransactOpts, spender, addedValue)
+}
+
+// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351.
+//
+// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactorSession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.IncreaseAllowance(&_FeeCurrency.TransactOpts, spender, addedValue)
+}
+
+// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb.
+//
+// Solidity: function transfer(address to, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactor) Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "transfer", to, amount)
+}
+
+// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb.
+//
+// Solidity: function transfer(address to, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencySession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.Transfer(&_FeeCurrency.TransactOpts, to, amount)
+}
+
+// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb.
+//
+// Solidity: function transfer(address to, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactorSession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.Transfer(&_FeeCurrency.TransactOpts, to, amount)
+}
+
+// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd.
+//
+// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.contract.Transact(opts, "transferFrom", from, to, amount)
+}
+
+// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd.
+//
+// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencySession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.TransferFrom(&_FeeCurrency.TransactOpts, from, to, amount)
+}
+
+// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd.
+//
+// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool)
+func (_FeeCurrency *FeeCurrencyTransactorSession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) {
+ return _FeeCurrency.Contract.TransferFrom(&_FeeCurrency.TransactOpts, from, to, amount)
+}
+
+// FeeCurrencyApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the FeeCurrency contract.
+type FeeCurrencyApprovalIterator struct {
+ Event *FeeCurrencyApproval // Event containing the contract specifics and raw log
+
+ contract *bind.BoundContract // Generic contract to use for unpacking event data
+ event string // Event name to use for unpacking event data
+
+ logs chan types.Log // Log channel receiving the found contract events
+ sub ethereum.Subscription // Subscription for errors, completion and termination
+ done bool // Whether the subscription completed delivering logs
+ fail error // Occurred error to stop iteration
+}
+
+// Next advances the iterator to the subsequent event, returning whether there
+// are any more events found. In case of a retrieval or parsing error, false is
+// returned and Error() can be queried for the exact failure.
+func (it *FeeCurrencyApprovalIterator) Next() bool {
+ // If the iterator failed, stop iterating
+ if it.fail != nil {
+ return false
+ }
+ // If the iterator completed, deliver directly whatever's available
+ if it.done {
+ select {
+ case log := <-it.logs:
+ it.Event = new(FeeCurrencyApproval)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ default:
+ return false
+ }
+ }
+ // Iterator still in progress, wait for either a data or an error event
+ select {
+ case log := <-it.logs:
+ it.Event = new(FeeCurrencyApproval)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ case err := <-it.sub.Err():
+ it.done = true
+ it.fail = err
+ return it.Next()
+ }
+}
+
+// Error returns any retrieval or parsing error occurred during filtering.
+func (it *FeeCurrencyApprovalIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *FeeCurrencyApprovalIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// FeeCurrencyApproval represents a Approval event raised by the FeeCurrency contract.
+type FeeCurrencyApproval struct {
+ Owner common.Address
+ Spender common.Address
+ Value *big.Int
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925.
+//
+// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value)
+func (_FeeCurrency *FeeCurrencyFilterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*FeeCurrencyApprovalIterator, error) {
+
+ var ownerRule []interface{}
+ for _, ownerItem := range owner {
+ ownerRule = append(ownerRule, ownerItem)
+ }
+ var spenderRule []interface{}
+ for _, spenderItem := range spender {
+ spenderRule = append(spenderRule, spenderItem)
+ }
+
+ logs, sub, err := _FeeCurrency.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyApprovalIterator{contract: _FeeCurrency.contract, event: "Approval", logs: logs, sub: sub}, nil
+}
+
+// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925.
+//
+// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value)
+func (_FeeCurrency *FeeCurrencyFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *FeeCurrencyApproval, owner []common.Address, spender []common.Address) (event.Subscription, error) {
+
+ var ownerRule []interface{}
+ for _, ownerItem := range owner {
+ ownerRule = append(ownerRule, ownerItem)
+ }
+ var spenderRule []interface{}
+ for _, spenderItem := range spender {
+ spenderRule = append(spenderRule, spenderItem)
+ }
+
+ logs, sub, err := _FeeCurrency.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule)
+ if err != nil {
+ return nil, err
+ }
+ return event.NewSubscription(func(quit <-chan struct{}) error {
+ defer sub.Unsubscribe()
+ for {
+ select {
+ case log := <-logs:
+ // New log arrived, parse the event and forward to the user
+ event := new(FeeCurrencyApproval)
+ if err := _FeeCurrency.contract.UnpackLog(event, "Approval", log); err != nil {
+ return err
+ }
+ event.Raw = log
+
+ select {
+ case sink <- event:
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ }
+ }), nil
+}
+
+// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925.
+//
+// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value)
+func (_FeeCurrency *FeeCurrencyFilterer) ParseApproval(log types.Log) (*FeeCurrencyApproval, error) {
+ event := new(FeeCurrencyApproval)
+ if err := _FeeCurrency.contract.UnpackLog(event, "Approval", log); err != nil {
+ return nil, err
+ }
+ event.Raw = log
+ return event, nil
+}
+
+// FeeCurrencyTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the FeeCurrency contract.
+type FeeCurrencyTransferIterator struct {
+ Event *FeeCurrencyTransfer // Event containing the contract specifics and raw log
+
+ contract *bind.BoundContract // Generic contract to use for unpacking event data
+ event string // Event name to use for unpacking event data
+
+ logs chan types.Log // Log channel receiving the found contract events
+ sub ethereum.Subscription // Subscription for errors, completion and termination
+ done bool // Whether the subscription completed delivering logs
+ fail error // Occurred error to stop iteration
+}
+
+// Next advances the iterator to the subsequent event, returning whether there
+// are any more events found. In case of a retrieval or parsing error, false is
+// returned and Error() can be queried for the exact failure.
+func (it *FeeCurrencyTransferIterator) Next() bool {
+ // If the iterator failed, stop iterating
+ if it.fail != nil {
+ return false
+ }
+ // If the iterator completed, deliver directly whatever's available
+ if it.done {
+ select {
+ case log := <-it.logs:
+ it.Event = new(FeeCurrencyTransfer)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ default:
+ return false
+ }
+ }
+ // Iterator still in progress, wait for either a data or an error event
+ select {
+ case log := <-it.logs:
+ it.Event = new(FeeCurrencyTransfer)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ case err := <-it.sub.Err():
+ it.done = true
+ it.fail = err
+ return it.Next()
+ }
+}
+
+// Error returns any retrieval or parsing error occurred during filtering.
+func (it *FeeCurrencyTransferIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *FeeCurrencyTransferIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// FeeCurrencyTransfer represents a Transfer event raised by the FeeCurrency contract.
+type FeeCurrencyTransfer struct {
+ From common.Address
+ To common.Address
+ Value *big.Int
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
+//
+// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
+func (_FeeCurrency *FeeCurrencyFilterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*FeeCurrencyTransferIterator, error) {
+
+ var fromRule []interface{}
+ for _, fromItem := range from {
+ fromRule = append(fromRule, fromItem)
+ }
+ var toRule []interface{}
+ for _, toItem := range to {
+ toRule = append(toRule, toItem)
+ }
+
+ logs, sub, err := _FeeCurrency.contract.FilterLogs(opts, "Transfer", fromRule, toRule)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyTransferIterator{contract: _FeeCurrency.contract, event: "Transfer", logs: logs, sub: sub}, nil
+}
+
+// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
+//
+// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
+func (_FeeCurrency *FeeCurrencyFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *FeeCurrencyTransfer, from []common.Address, to []common.Address) (event.Subscription, error) {
+
+ var fromRule []interface{}
+ for _, fromItem := range from {
+ fromRule = append(fromRule, fromItem)
+ }
+ var toRule []interface{}
+ for _, toItem := range to {
+ toRule = append(toRule, toItem)
+ }
+
+ logs, sub, err := _FeeCurrency.contract.WatchLogs(opts, "Transfer", fromRule, toRule)
+ if err != nil {
+ return nil, err
+ }
+ return event.NewSubscription(func(quit <-chan struct{}) error {
+ defer sub.Unsubscribe()
+ for {
+ select {
+ case log := <-logs:
+ // New log arrived, parse the event and forward to the user
+ event := new(FeeCurrencyTransfer)
+ if err := _FeeCurrency.contract.UnpackLog(event, "Transfer", log); err != nil {
+ return err
+ }
+ event.Raw = log
+
+ select {
+ case sink <- event:
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ }
+ }), nil
+}
+
+// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
+//
+// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
+func (_FeeCurrency *FeeCurrencyFilterer) ParseTransfer(log types.Log) (*FeeCurrencyTransfer, error) {
+ event := new(FeeCurrencyTransfer)
+ if err := _FeeCurrency.contract.UnpackLog(event, "Transfer", log); err != nil {
+ return nil, err
+ }
+ event.Raw = log
+ return event, nil
+}
diff --git op-geth/contracts/celo/abigen/FeeCurrencyDirectory.go Celo/contracts/celo/abigen/FeeCurrencyDirectory.go
new file mode 100644
index 0000000000000000000000000000000000000000..d79c9bba15b1259761880cdeded76868b1c7108f
--- /dev/null
+++ Celo/contracts/celo/abigen/FeeCurrencyDirectory.go
@@ -0,0 +1,294 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package abigen
+
+import (
+ "errors"
+ "math/big"
+ "strings"
+
+ ethereum "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var (
+ _ = errors.New
+ _ = big.NewInt
+ _ = strings.NewReader
+ _ = ethereum.NotFound
+ _ = bind.Bind
+ _ = common.Big1
+ _ = types.BloomLookup
+ _ = event.NewSubscription
+ _ = abi.ConvertType
+)
+
+// IFeeCurrencyDirectoryCurrencyConfig is an auto generated low-level Go binding around an user-defined struct.
+type IFeeCurrencyDirectoryCurrencyConfig struct {
+ Oracle common.Address
+ IntrinsicGas *big.Int
+}
+
+// FeeCurrencyDirectoryMetaData contains all meta data concerning the FeeCurrencyDirectory contract.
+var FeeCurrencyDirectoryMetaData = &bind.MetaData{
+ ABI: "[{\"type\":\"function\",\"name\":\"getCurrencies\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrencyConfig\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIFeeCurrencyDirectory.CurrencyConfig\",\"components\":[{\"name\":\"oracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"intrinsicGas\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getExchangeRate\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"numerator\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"denominator\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"}]",
+}
+
+// FeeCurrencyDirectoryABI is the input ABI used to generate the binding from.
+// Deprecated: Use FeeCurrencyDirectoryMetaData.ABI instead.
+var FeeCurrencyDirectoryABI = FeeCurrencyDirectoryMetaData.ABI
+
+// FeeCurrencyDirectory is an auto generated Go binding around an Ethereum contract.
+type FeeCurrencyDirectory struct {
+ FeeCurrencyDirectoryCaller // Read-only binding to the contract
+ FeeCurrencyDirectoryTransactor // Write-only binding to the contract
+ FeeCurrencyDirectoryFilterer // Log filterer for contract events
+}
+
+// FeeCurrencyDirectoryCaller is an auto generated read-only Go binding around an Ethereum contract.
+type FeeCurrencyDirectoryCaller struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// FeeCurrencyDirectoryTransactor is an auto generated write-only Go binding around an Ethereum contract.
+type FeeCurrencyDirectoryTransactor struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// FeeCurrencyDirectoryFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
+type FeeCurrencyDirectoryFilterer struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// FeeCurrencyDirectorySession is an auto generated Go binding around an Ethereum contract,
+// with pre-set call and transact options.
+type FeeCurrencyDirectorySession struct {
+ Contract *FeeCurrencyDirectory // Generic contract binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// FeeCurrencyDirectoryCallerSession is an auto generated read-only Go binding around an Ethereum contract,
+// with pre-set call options.
+type FeeCurrencyDirectoryCallerSession struct {
+ Contract *FeeCurrencyDirectoryCaller // Generic contract caller binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+}
+
+// FeeCurrencyDirectoryTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
+// with pre-set transact options.
+type FeeCurrencyDirectoryTransactorSession struct {
+ Contract *FeeCurrencyDirectoryTransactor // Generic contract transactor binding to set the session for
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// FeeCurrencyDirectoryRaw is an auto generated low-level Go binding around an Ethereum contract.
+type FeeCurrencyDirectoryRaw struct {
+ Contract *FeeCurrencyDirectory // Generic contract binding to access the raw methods on
+}
+
+// FeeCurrencyDirectoryCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
+type FeeCurrencyDirectoryCallerRaw struct {
+ Contract *FeeCurrencyDirectoryCaller // Generic read-only contract binding to access the raw methods on
+}
+
+// FeeCurrencyDirectoryTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
+type FeeCurrencyDirectoryTransactorRaw struct {
+ Contract *FeeCurrencyDirectoryTransactor // Generic write-only contract binding to access the raw methods on
+}
+
+// NewFeeCurrencyDirectory creates a new instance of FeeCurrencyDirectory, bound to a specific deployed contract.
+func NewFeeCurrencyDirectory(address common.Address, backend bind.ContractBackend) (*FeeCurrencyDirectory, error) {
+ contract, err := bindFeeCurrencyDirectory(address, backend, backend, backend)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyDirectory{FeeCurrencyDirectoryCaller: FeeCurrencyDirectoryCaller{contract: contract}, FeeCurrencyDirectoryTransactor: FeeCurrencyDirectoryTransactor{contract: contract}, FeeCurrencyDirectoryFilterer: FeeCurrencyDirectoryFilterer{contract: contract}}, nil
+}
+
+// NewFeeCurrencyDirectoryCaller creates a new read-only instance of FeeCurrencyDirectory, bound to a specific deployed contract.
+func NewFeeCurrencyDirectoryCaller(address common.Address, caller bind.ContractCaller) (*FeeCurrencyDirectoryCaller, error) {
+ contract, err := bindFeeCurrencyDirectory(address, caller, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyDirectoryCaller{contract: contract}, nil
+}
+
+// NewFeeCurrencyDirectoryTransactor creates a new write-only instance of FeeCurrencyDirectory, bound to a specific deployed contract.
+func NewFeeCurrencyDirectoryTransactor(address common.Address, transactor bind.ContractTransactor) (*FeeCurrencyDirectoryTransactor, error) {
+ contract, err := bindFeeCurrencyDirectory(address, nil, transactor, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyDirectoryTransactor{contract: contract}, nil
+}
+
+// NewFeeCurrencyDirectoryFilterer creates a new log filterer instance of FeeCurrencyDirectory, bound to a specific deployed contract.
+func NewFeeCurrencyDirectoryFilterer(address common.Address, filterer bind.ContractFilterer) (*FeeCurrencyDirectoryFilterer, error) {
+ contract, err := bindFeeCurrencyDirectory(address, nil, nil, filterer)
+ if err != nil {
+ return nil, err
+ }
+ return &FeeCurrencyDirectoryFilterer{contract: contract}, nil
+}
+
+// bindFeeCurrencyDirectory binds a generic wrapper to an already deployed contract.
+func bindFeeCurrencyDirectory(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+ parsed, err := FeeCurrencyDirectoryMetaData.GetAbi()
+ if err != nil {
+ return nil, err
+ }
+ return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _FeeCurrencyDirectory.Contract.FeeCurrencyDirectoryCaller.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _FeeCurrencyDirectory.Contract.FeeCurrencyDirectoryTransactor.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _FeeCurrencyDirectory.Contract.FeeCurrencyDirectoryTransactor.contract.Transact(opts, method, params...)
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _FeeCurrencyDirectory.Contract.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _FeeCurrencyDirectory.Contract.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _FeeCurrencyDirectory.Contract.contract.Transact(opts, method, params...)
+}
+
+// GetCurrencies is a free data retrieval call binding the contract method 0x61c661de.
+//
+// Solidity: function getCurrencies() view returns(address[])
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCaller) GetCurrencies(opts *bind.CallOpts) ([]common.Address, error) {
+ var out []interface{}
+ err := _FeeCurrencyDirectory.contract.Call(opts, &out, "getCurrencies")
+
+ if err != nil {
+ return *new([]common.Address), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address)
+
+ return out0, err
+
+}
+
+// GetCurrencies is a free data retrieval call binding the contract method 0x61c661de.
+//
+// Solidity: function getCurrencies() view returns(address[])
+func (_FeeCurrencyDirectory *FeeCurrencyDirectorySession) GetCurrencies() ([]common.Address, error) {
+ return _FeeCurrencyDirectory.Contract.GetCurrencies(&_FeeCurrencyDirectory.CallOpts)
+}
+
+// GetCurrencies is a free data retrieval call binding the contract method 0x61c661de.
+//
+// Solidity: function getCurrencies() view returns(address[])
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCallerSession) GetCurrencies() ([]common.Address, error) {
+ return _FeeCurrencyDirectory.Contract.GetCurrencies(&_FeeCurrencyDirectory.CallOpts)
+}
+
+// GetCurrencyConfig is a free data retrieval call binding the contract method 0xeab43d97.
+//
+// Solidity: function getCurrencyConfig(address token) view returns((address,uint256))
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCaller) GetCurrencyConfig(opts *bind.CallOpts, token common.Address) (IFeeCurrencyDirectoryCurrencyConfig, error) {
+ var out []interface{}
+ err := _FeeCurrencyDirectory.contract.Call(opts, &out, "getCurrencyConfig", token)
+
+ if err != nil {
+ return *new(IFeeCurrencyDirectoryCurrencyConfig), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(IFeeCurrencyDirectoryCurrencyConfig)).(*IFeeCurrencyDirectoryCurrencyConfig)
+
+ return out0, err
+
+}
+
+// GetCurrencyConfig is a free data retrieval call binding the contract method 0xeab43d97.
+//
+// Solidity: function getCurrencyConfig(address token) view returns((address,uint256))
+func (_FeeCurrencyDirectory *FeeCurrencyDirectorySession) GetCurrencyConfig(token common.Address) (IFeeCurrencyDirectoryCurrencyConfig, error) {
+ return _FeeCurrencyDirectory.Contract.GetCurrencyConfig(&_FeeCurrencyDirectory.CallOpts, token)
+}
+
+// GetCurrencyConfig is a free data retrieval call binding the contract method 0xeab43d97.
+//
+// Solidity: function getCurrencyConfig(address token) view returns((address,uint256))
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCallerSession) GetCurrencyConfig(token common.Address) (IFeeCurrencyDirectoryCurrencyConfig, error) {
+ return _FeeCurrencyDirectory.Contract.GetCurrencyConfig(&_FeeCurrencyDirectory.CallOpts, token)
+}
+
+// GetExchangeRate is a free data retrieval call binding the contract method 0xefb7601d.
+//
+// Solidity: function getExchangeRate(address token) view returns(uint256 numerator, uint256 denominator)
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCaller) GetExchangeRate(opts *bind.CallOpts, token common.Address) (struct {
+ Numerator *big.Int
+ Denominator *big.Int
+}, error) {
+ var out []interface{}
+ err := _FeeCurrencyDirectory.contract.Call(opts, &out, "getExchangeRate", token)
+
+ outstruct := new(struct {
+ Numerator *big.Int
+ Denominator *big.Int
+ })
+ if err != nil {
+ return *outstruct, err
+ }
+
+ outstruct.Numerator = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+ outstruct.Denominator = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int)
+
+ return *outstruct, err
+
+}
+
+// GetExchangeRate is a free data retrieval call binding the contract method 0xefb7601d.
+//
+// Solidity: function getExchangeRate(address token) view returns(uint256 numerator, uint256 denominator)
+func (_FeeCurrencyDirectory *FeeCurrencyDirectorySession) GetExchangeRate(token common.Address) (struct {
+ Numerator *big.Int
+ Denominator *big.Int
+}, error) {
+ return _FeeCurrencyDirectory.Contract.GetExchangeRate(&_FeeCurrencyDirectory.CallOpts, token)
+}
+
+// GetExchangeRate is a free data retrieval call binding the contract method 0xefb7601d.
+//
+// Solidity: function getExchangeRate(address token) view returns(uint256 numerator, uint256 denominator)
+func (_FeeCurrencyDirectory *FeeCurrencyDirectoryCallerSession) GetExchangeRate(token common.Address) (struct {
+ Numerator *big.Int
+ Denominator *big.Int
+}, error) {
+ return _FeeCurrencyDirectory.Contract.GetExchangeRate(&_FeeCurrencyDirectory.CallOpts, token)
+}
diff --git op-geth/contracts/celo/celo.go Celo/contracts/celo/celo.go
new file mode 100644
index 0000000000000000000000000000000000000000..eee8c63da824cc170cbabbe8e6f17f199d10ab2f
--- /dev/null
+++ Celo/contracts/celo/celo.go
@@ -0,0 +1,18 @@
+package celo
+
+import _ "embed"
+
+//go:generate go run ../../cmd/abigen --pkg abigen --out abigen/FeeCurrency.go --abi compiled/FeeCurrency.abi --type FeeCurrency
+//go:generate go run ../../cmd/abigen --pkg abigen --out abigen/FeeCurrencyDirectory.go --abi compiled/IFeeCurrencyDirectory.abi --type FeeCurrencyDirectory
+
+//go:embed compiled/GoldToken.bin-runtime
+var CeloTokenBytecodeRaw []byte
+
+//go:embed compiled/FeeCurrency.bin-runtime
+var FeeCurrencyBytecodeRaw []byte
+
+//go:embed compiled/FeeCurrencyDirectory.bin-runtime
+var FeeCurrencyDirectoryBytecodeRaw []byte
+
+//go:embed compiled/MockOracle.bin-runtime
+var MockOracleBytecodeRaw []byte
diff --git op-geth/contracts/celo/compiled/FeeCurrency.abi Celo/contracts/celo/compiled/FeeCurrency.abi
new file mode 100644
index 0000000000000000000000000000000000000000..279f6f754ab9192a2b8eb5f90a3aec6639c5e551
--- /dev/null
+++ Celo/contracts/celo/compiled/FeeCurrency.abi
@@ -0,0 +1,354 @@
+[
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "name": "name_",
+ "type": "string",
+ "internalType": "string"
+ },
+ {
+ "name": "symbol_",
+ "type": "string",
+ "internalType": "string"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "allowance",
+ "inputs": [
+ {
+ "name": "owner",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "approve",
+ "inputs": [
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "balanceOf",
+ "inputs": [
+ {
+ "name": "account",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "creditGasFees",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "feeRecipient",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "communityFund",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "refund",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "tipTxFee",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "baseTxFee",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "debitGasFees",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "decimals",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint8",
+ "internalType": "uint8"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "decreaseAllowance",
+ "inputs": [
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "subtractedValue",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "increaseAllowance",
+ "inputs": [
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "addedValue",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "name",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "string",
+ "internalType": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "symbol",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "string",
+ "internalType": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "transfer",
+ "inputs": [
+ {
+ "name": "to",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferFrom",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "Approval",
+ "inputs": [
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "indexed": false,
+ "internalType": "uint256"
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Transfer",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "indexed": false,
+ "internalType": "uint256"
+ }
+ ],
+ "anonymous": false
+ }
+]
diff --git op-geth/contracts/celo/compiled/FeeCurrency.bin-runtime Celo/contracts/celo/compiled/FeeCurrency.bin-runtime
new file mode 100644
index 0000000000000000000000000000000000000000..236826b180339f87deee821e74369a225b3d85db
--- /dev/null
+++ Celo/contracts/celo/compiled/FeeCurrency.bin-runtime
@@ -0,0 +1 @@
+0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806358cf96721161008c57806395d89b411161006657806395d89b41146101ca578063a457c2d7146101d2578063a9059cbb146101e5578063dd62ed3e146101f857600080fd5b806358cf96721461016c5780636a30b2531461018157806370a082311461019457600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015957600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f99190610c15565b60405180910390f35b610115610110366004610cb1565b6102d0565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610cdb565b6102e8565b604051601281526020016100f9565b610115610167366004610cb1565b61030e565b61017f61017a366004610cb1565b61035a565b005b61017f61018f366004610d17565b61041e565b6101296101a2366004610d8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6100ec610510565b6101156101e0366004610cb1565b61051f565b6101156101f3366004610cb1565b6105fb565b610129610206366004610daa565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461024d90610ddd565b80601f016020809104026020016040519081016040528092919081815260200182805461027990610ddd565b80156102c65780601f1061029b576101008083540402835291602001916102c6565b820191906000526020600020905b8154815290600101906020018083116102a957829003601f168201915b5050505050905090565b6000336102de818585610609565b5060019392505050565b6000336102f68582856107bc565b610301858585610893565b60019150505b9392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906102de9082908690610355908790610e5f565b610609565b33156103c7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906103fc908490610e77565b9250508190555080600260008282546104159190610e77565b90915550505050565b3315610486576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064016103be565b73ffffffffffffffffffffffffffffffffffffffff8816600090815260208190526040812080548692906104bb908490610e5f565b909155506104cc9050888683610b46565b6104d69085610e5f565b93506104e3888885610b46565b6104ed9085610e5f565b935083600260008282546105019190610e5f565b90915550505050505050505050565b60606004805461024d90610ddd565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909190838110156105e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016103be565b6105f08286868403610609565b506001949350505050565b6000336102de818585610893565b73ffffffffffffffffffffffffffffffffffffffff83166106ab576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff821661074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461088d5781811015610880576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103be565b61088d8484848403610609565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610936576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff82166109d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220858503905591851681529081208054849290610ad3908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b3991815260200190565b60405180910390a361088d565b600073ffffffffffffffffffffffffffffffffffffffff8316610b6b57506000610307565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208054849290610ba0908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c0691815260200190565b60405180910390a35092915050565b600060208083528351808285015260005b81811015610c4257858101830151858201604001528201610c26565b81811115610c54576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c88565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c88565b9250610d0760208501610c88565b9150604084013590509250925092565b600080600080600080600080610100898b031215610d3457600080fd5b610d3d89610c88565b9750610d4b60208a01610c88565b9650610d5960408a01610c88565b9550610d6760608a01610c88565b979a969950949760808101359660a0820135965060c0820135955060e0909101359350915050565b600060208284031215610da157600080fd5b61030782610c88565b60008060408385031215610dbd57600080fd5b610dc683610c88565b9150610dd460208401610c88565b90509250929050565b600181811c90821680610df157607f821691505b602082108103610e2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610e7257610e72610e30565b500190565b600082821015610e8957610e89610e30565b50039056fea164736f6c634300080f000a
diff --git op-geth/contracts/celo/compiled/FeeCurrencyDirectory.bin-runtime Celo/contracts/celo/compiled/FeeCurrencyDirectory.bin-runtime
new file mode 100644
index 0000000000000000000000000000000000000000..79b9c0731e3d8bb39908819291fcd1f31d5d2ce0
--- /dev/null
+++ Celo/contracts/celo/compiled/FeeCurrencyDirectory.bin-runtime
@@ -0,0 +1 @@
+0x608060405234801561001057600080fd5b50600436106100b45760003560e01c8063715018a611610071578063715018a6146101905780638129fc1c146101985780638da5cb5b146101a0578063eab43d97146101c9578063efb7601d14610245578063f2fde38b1461026d57600080fd5b8063158ef93e146100b957806316be73a8146100db578063216ab7df146100f057806354255be0146101035780636036cba31461012957806361c661de1461017b575b600080fd5b6000546100c69060ff1681565b60405190151581526020015b60405180910390f35b6100ee6100e9366004610939565b610280565b005b6100ee6100fe366004610963565b61045d565b6001806000806040805194855260208501939093529183015260608201526080016100d2565b61015c61013736600461099f565b600160208190526000918252604090912080549101546001600160a01b039091169082565b604080516001600160a01b0390931683526020830191909152016100d2565b61018361062a565b6040516100d291906109c1565b6100ee61068c565b6100ee6106c8565b60005461010090046001600160a01b03166040516001600160a01b0390911681526020016100d2565b6102216101d736600461099f565b604080518082018252600080825260209182018190526001600160a01b03938416815260018083529083902083518085019094528054909416835292909201549181019190915290565b6040805182516001600160a01b0316815260209283015192810192909252016100d2565b61025861025336600461099f565b610731565b604080519283526020830191909152016100d2565b6100ee61027b36600461099f565b610823565b6000546001600160a01b036101009091041633146102b95760405162461bcd60e51b81526004016102b090610a0e565b60405180910390fd5b60025481106103005760405162461bcd60e51b8152602060048201526013602482015272496e646578206f7574206f6620626f756e647360681b60448201526064016102b0565b816001600160a01b03166002828154811061031d5761031d610a43565b6000918252602090912001546001600160a01b03161461037f5760405162461bcd60e51b815260206004820152601a60248201527f496e64657820646f6573206e6f74206d6174636820746f6b656e00000000000060448201526064016102b0565b6001600160a01b0382166000908152600160208190526040822080546001600160a01b03191681558101919091556002805490916103bc91610a59565b815481106103cc576103cc610a43565b600091825260209091200154600280546001600160a01b0390921691839081106103f8576103f8610a43565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b03160217905550600280548061043757610437610a80565b600082815260209020810160001990810180546001600160a01b03191690550190555050565b6000546001600160a01b0361010090910416331461048d5760405162461bcd60e51b81526004016102b090610a0e565b6001600160a01b0382166104e35760405162461bcd60e51b815260206004820152601d60248201527f4f7261636c6520616464726573732063616e6e6f74206265207a65726f00000060448201526064016102b0565b600081116105335760405162461bcd60e51b815260206004820152601c60248201527f496e7472696e736963206761732063616e6e6f74206265207a65726f0000000060448201526064016102b0565b6001600160a01b0383811660009081526001602052604090205416156105a55760405162461bcd60e51b815260206004820152602160248201527f43757272656e637920616c726561647920696e20746865206469726563746f726044820152607960f81b60648201526084016102b0565b6040805180820182526001600160a01b039384168152602080820193845294841660008181526001968790529283209151825495166001600160a01b031995861617825592519085015560028054948501815590527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90920180549091169091179055565b6060600280548060200260200160405190810160405280929190818152602001828054801561068257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610664575b5050505050905090565b6000546001600160a01b036101009091041633146106bc5760405162461bcd60e51b81526004016102b090610a0e565b6106c660006108c4565b565b60005460ff161561071b5760405162461bcd60e51b815260206004820152601c60248201527f636f6e747261637420616c726561647920696e697469616c697a65640000000060448201526064016102b0565b6000805460ff191660011790556106c6336108c4565b6001600160a01b03818116600090815260016020526040812054909182911661079c5760405162461bcd60e51b815260206004820152601d60248201527f43757272656e6379206e6f7420696e20746865206469726563746f727900000060448201526064016102b0565b6001600160a01b038381166000818152600160205260409081902054905163efb7601d60e01b815260048101929092529091169063efb7601d906024016040805180830381865afa1580156107f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108199190610a96565b9094909350915050565b6000546001600160a01b036101009091041633146108535760405162461bcd60e51b81526004016102b090610a0e565b6001600160a01b0381166108b85760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016102b0565b6108c1816108c4565b50565b600080546001600160a01b03838116610100818102610100600160a81b0319851617855560405193049190911692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a35050565b80356001600160a01b038116811461093457600080fd5b919050565b6000806040838503121561094c57600080fd5b6109558361091d565b946020939093013593505050565b60008060006060848603121561097857600080fd5b6109818461091d565b925061098f6020850161091d565b9150604084013590509250925092565b6000602082840312156109b157600080fd5b6109ba8261091d565b9392505050565b6020808252825182820181905260009190848201906040850190845b81811015610a025783516001600160a01b0316835292840192918401916001016109dd565b50909695505050505050565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b634e487b7160e01b600052603260045260246000fd5b81810381811115610a7a57634e487b7160e01b600052601160045260246000fd5b92915050565b634e487b7160e01b600052603160045260246000fd5b60008060408385031215610aa957600080fd5b50508051602090910151909290915056fea2646970667358221220127159ea8f76efe84815c2177266f0115f42dfbdd3b1fd1624548e208504750e64736f6c63430008130033
diff --git op-geth/contracts/celo/compiled/GoldToken.abi Celo/contracts/celo/compiled/GoldToken.abi
new file mode 100644
index 0000000000000000000000000000000000000000..d0d8926dac24f7446be224c6d0494d6bf799c8cd
--- /dev/null
+++ Celo/contracts/celo/compiled/GoldToken.abi
@@ -0,0 +1,669 @@
+[
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "name": "test",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "allocatedSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "allowance",
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "approve",
+ "inputs": [
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "balanceOf",
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "burn",
+ "inputs": [
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "celoTokenDistributionSchedule",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "contract ICeloDistributionSchedule"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "circulatingSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "decimals",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint8",
+ "internalType": "uint8"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "decreaseAllowance",
+ "inputs": [
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "depositAmount",
+ "inputs": [
+ {
+ "name": "_depositAmount",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "getBurnedAmount",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getVersionNumber",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "pure"
+ },
+ {
+ "type": "function",
+ "name": "increaseAllowance",
+ "inputs": [
+ {
+ "name": "spender",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "increaseSupply",
+ "inputs": [
+ {
+ "name": "amount",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "initialize",
+ "inputs": [
+ {
+ "name": "registryAddress",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "initialized",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "isL2",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "isOwner",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "mint",
+ "inputs": [
+ {
+ "name": "to",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "name",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "string",
+ "internalType": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "registry",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "contract IRegistry"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "renounceOwnership",
+ "inputs": [],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setCeloTokenDistributionScheduleAddress",
+ "inputs": [
+ {
+ "name": "celoTokenDistributionScheduleAddress",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setRegistry",
+ "inputs": [
+ {
+ "name": "registryAddress",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "symbol",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "string",
+ "internalType": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "transfer",
+ "inputs": [
+ {
+ "name": "to",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferFrom",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferOwnership",
+ "inputs": [
+ {
+ "name": "newOwner",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferWithComment",
+ "inputs": [
+ {
+ "name": "to",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "comment",
+ "type": "string",
+ "internalType": "string"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool",
+ "internalType": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "withdrawAmount",
+ "inputs": [
+ {
+ "name": "_withdrawAmount",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "withdrawn",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "event",
+ "name": "Approval",
+ "inputs": [
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "indexed": false,
+ "internalType": "uint256"
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnershipTransferred",
+ "inputs": [
+ {
+ "name": "previousOwner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "RegistrySet",
+ "inputs": [
+ {
+ "name": "registryAddress",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "SetCeloTokenDistributionScheduleAddress",
+ "inputs": [
+ {
+ "name": "newScheduleAddress",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Transfer",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "value",
+ "type": "uint256",
+ "indexed": false,
+ "internalType": "uint256"
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "TransferComment",
+ "inputs": [
+ {
+ "name": "comment",
+ "type": "string",
+ "indexed": false,
+ "internalType": "string"
+ }
+ ],
+ "anonymous": false
+ }
+]
diff --git op-geth/contracts/celo/compiled/GoldToken.bin-runtime Celo/contracts/celo/compiled/GoldToken.bin-runtime
new file mode 100644
index 0000000000000000000000000000000000000000..6417be2913ecbddc6540400a74de1524bf696bc1
--- /dev/null
+++ Celo/contracts/celo/compiled/GoldToken.bin-runtime
@@ -0,0 +1 @@
+0x608060405234801561001057600080fd5b50600436106101fb5760003560e01c80637b1039991161011a578063a91ee0dc116100ad578063d4d83cfb1161007c578063d4d83cfb14610565578063db2b4d101461056d578063dd62ed3e14610593578063e1d6aceb146105c1578063f2fde38b14610646576101fb565b8063a91ee0dc146104f4578063b921e1631461051a578063c4d66de814610537578063c80ec5221461055d576101fb565b80639358928b116100e95780639358928b1461048c57806395d89b4114610494578063a457c2d71461049c578063a9059cbb146104c8576101fb565b80637b1039991461043b57806387f8ab261461045f5780638da5cb5b1461047c5780638f32d59b14610484576101fb565b8063395093511161019257806354255be01161016157806354255be0146103d757806370a0823114610405578063715018a61461042b57806376348f7114610433576101fb565b8063395093511461035a5780633a70a5ca1461038657806340c10f191461038e57806342966c68146103ba576101fb565b806318160ddd116101ce57806318160ddd146102e457806323b872dd146102fe578063265126bd14610334578063313ce5671461033c576101fb565b80630562b9f71461020057806306fdde031461021f578063095ea7b31461029c578063158ef93e146102dc575b600080fd5b61021d6004803603602081101561021657600080fd5b503561066c565b005b61022761070d565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610261578181015183820152602001610249565b50505050905090810190601f16801561028e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102c8600480360360408110156102b257600080fd5b506001600160a01b038135169060200135610739565b604080519115158252519081900360200190f35b6102c86107fe565b6102ec610807565b60408051918252519081900360200190f35b6102c86004803603606081101561031457600080fd5b506001600160a01b03813581169160208101359091169060400135610846565b6102ec610b17565b610344610b29565b6040805160ff9092168252519081900360200190f35b6102c86004803603604081101561037057600080fd5b506001600160a01b038135169060200135610b2e565b6102ec610c2a565b6102c8600480360360408110156103a457600080fd5b506001600160a01b038135169060200135610c8e565b6102c8600480360360208110156103d057600080fd5b5035610eb6565b6103df610ec4565b604080519485526020850193909352838301919091526060830152519081900360800190f35b6102ec6004803603602081101561041b57600080fd5b50356001600160a01b0316610ed1565b61021d610ede565b6102c8610f74565b610443610f88565b604080516001600160a01b039092168252519081900360200190f35b61021d6004803603602081101561047557600080fd5b5035610f97565b610443611033565b6102c8611047565b6102ec611070565b61022761109e565b6102c8600480360360408110156104b257600080fd5b506001600160a01b0381351690602001356110bc565b6102c8600480360360408110156104de57600080fd5b506001600160a01b0381351690602001356110f1565b61021d6004803603602081101561050a57600080fd5b50356001600160a01b0316611104565b61021d6004803603602081101561053057600080fd5b50356111f0565b61021d6004803603602081101561054d57600080fd5b50356001600160a01b0316611257565b6102ec6112d4565b6104436112da565b61021d6004803603602081101561058357600080fd5b50356001600160a01b03166112e9565b6102ec600480360360408110156105a957600080fd5b506001600160a01b03813581169160200135166113e3565b6102c8600480360360608110156105d757600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561060757600080fd5b82018360208201111561061957600080fd5b8035906020019184600183028401116401000000008311171561063b57600080fd5b50909250905061140e565b61021d6004803603602081101561065c57600080fd5b50356001600160a01b0316611486565b610674610f74565b6106af5760405162461bcd60e51b81526004018080602001828103825260238152602001806119e76023913960400191505060405180910390fd5b6016602160991b0133146106f45760405162461bcd60e51b81526004018080602001828103825260228152602001806119076022913960400191505060405180910390fd5b600454610707908263ffffffff6114d616565b60045550565b60408051808201909152601181527010d95b1bc81b985d1a5d9948185cdcd95d607a1b60208201525b90565b60006001600160a01b038316610796576040805162461bcd60e51b815260206004820152601a60248201527f63616e6e6f742073657420616c6c6f77616e636520666f722030000000000000604482015290519081900360640190fd5b3360008181526003602090815260408083206001600160a01b03881680855290835292819020869055805186815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60005460ff1681565b6000610811610f74565b1561083d57600454610836906b033b2e3c9fd0803ce80000009063ffffffff61153016565b9050610736565b50600254610736565b60006001600160a01b03831661088d5760405162461bcd60e51b815260040180806020018281038252602a815260200180611a0a602a913960400191505060405180910390fd5b61089684610ed1565b8211156108d45760405162461bcd60e51b81526004018080602001828103825260298152602001806119be6029913960400191505060405180910390fd5b6001600160a01b03841660009081526003602090815260408083203384529091529020548211156109365760405162461bcd60e51b8152600401808060200182810382526036815260200180611a346036913960400191505060405180910390fd5b600060fd815a9087878760405160200180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b0316815260200182815260200193505050506040516020818303038152906040526040518082805190602001908083835b602083106109c05780518252601f1990920191602091820191016109a1565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381858888f193505050503d8060008114610a23576040519150601f19603f3d011682016040523d82523d6000602084013e610a28565b606091505b50508091505080610a77576040805162461bcd60e51b815260206004820152601460248201527310d15313c81d1c985b9cd9995c8819985a5b195960621b604482015290519081900360640190fd5b6001600160a01b0385166000908152600360209081526040808320338452909152902054610aab908463ffffffff61153016565b6001600160a01b03808716600081815260036020908152604080832033845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3506001949350505050565b6000610b2461dead610ed1565b905090565b601290565b60006001600160a01b038316610b8b576040805162461bcd60e51b815260206004820152601a60248201527f63616e6e6f742073657420616c6c6f77616e636520666f722030000000000000604482015290519081900360640190fd5b3360009081526003602090815260408083206001600160a01b038716845290915281205490610bc0828563ffffffff6114d616565b3360008181526003602090815260408083206001600160a01b038b16808552908352928190208590558051858152905194955091937f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3506001949350505050565b6000610c34610f74565b610c6f5760405162461bcd60e51b81526004018080602001828103825260238152602001806119e76023913960400191505060405180910390fd5b506005546001600160a01b0316316b033b2e3c9fd0803ce80000000390565b6000610c98611572565b3315610cde576040805162461bcd60e51b815260206004820152601060248201526f13db9b1e4815934818d85b8818d85b1b60821b604482015290519081900360640190fd5b81610ceb575060016107f8565b6001600160a01b038316610d305760405162461bcd60e51b81526004018080602001828103825260268152602001806119296026913960400191505060405180910390fd5b600254610d43908363ffffffff6114d616565b600255600060fd815a6040805160006020808301919091526001600160a01b038a168284015260608083018a905283518084039091018152608090920192839052815193949391929182918401908083835b60208310610db45780518252601f199092019160209182019101610d95565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381858888f193505050503d8060008114610e17576040519150601f19603f3d011682016040523d82523d6000602084013e610e1c565b606091505b50508091505080610e6b576040805162461bcd60e51b815260206004820152601460248201527310d15313c81d1c985b9cd9995c8819985a5b195960621b604482015290519081900360640190fd5b6040805184815290516001600160a01b038616916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35060019392505050565b60006107f861dead836115b8565b6001806003600090919293565b6001600160a01b03163190565b610ee6611047565b610f25576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b600080546040516101009091046001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a360008054610100600160a81b0319169055565b63ffffffff6018602160991b013b16151590565b6001546001600160a01b031681565b3315610fdd576040805162461bcd60e51b815260206004820152601060248201526f13db9b1e4815934818d85b8818d85b1b60821b604482015290519081900360640190fd5b610fe5610f74565b6110205760405162461bcd60e51b81526004018080602001828103825260238152602001806119e76023913960400191505060405180910390fd5b600454610707908263ffffffff61153016565b60005461010090046001600160a01b031690565b6000805461010090046001600160a01b031661106161176f565b6001600160a01b031614905090565b6000610b2461107f6000610ed1565b61109261108a610b17565b611092610807565b9063ffffffff61153016565b60408051808201909152600481526343454c4f60e01b602082015290565b3360009081526003602090815260408083206001600160a01b038616845290915281205481610bc0828563ffffffff61153016565b60006110fd8383611773565b9392505050565b61110c611047565b61114b576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b6001600160a01b0381166111a6576040805162461bcd60e51b815260206004820181905260248201527f43616e6e6f7420726567697374657220746865206e756c6c2061646472657373604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b90600090a250565b6111f8611572565b331561123e576040805162461bcd60e51b815260206004820152601060248201526f13db9b1e4815934818d85b8818d85b1b60821b604482015290519081900360640190fd5b600254611251908263ffffffff6114d616565b60025550565b60005460ff16156112af576040805162461bcd60e51b815260206004820152601c60248201527f636f6e747261637420616c726561647920696e697469616c697a656400000000604482015290519081900360640190fd5b6000805460ff191660011781556002556112c8336117c4565b6112d181611104565b50565b60045481565b6005546001600160a01b031681565b6112f1611047565b611330576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b6001600160a01b03811615158061135557506005546001600160a01b03828116911614155b611399576040805162461bcd60e51b815260206004820152601060248201526f24b73b30b634b21030b2323932b9b99760811b604482015290519081900360640190fd5b600580546001600160a01b0319166001600160a01b0383169081179091556040517f4b0e16c81bce2248d2d60ed469d2fe74152253bde95ba850f22ae056723980a690600090a250565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b60008061141b8686611773565b90507fe5d4e30fb8364e57bc4d662a07d0cf36f4c34552004c4c3624620a2c1d1c03dc848460405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a195945050505050565b61148e611047565b6114cd576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b6112d1816117c4565b6000828201838110156110fd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60006110fd83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061186f565b61157a610f74565b156115b65760405162461bcd60e51b81526004018080602001828103825260298152602001806119756029913960400191505060405180910390fd5b565b60006115c333610ed1565b8211156116015760405162461bcd60e51b81526004018080602001828103825260298152602001806119be6029913960400191505060405180910390fd5b600060fd815a60408051336020808301919091526001600160a01b038a168284015260608083018a905283518084039091018152608090920192839052815193949391929182918401908083835b6020831061166e5780518252601f19909201916020918201910161164f565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381858888f193505050503d80600081146116d1576040519150601f19603f3d011682016040523d82523d6000602084013e6116d6565b606091505b50508091505080611725576040805162461bcd60e51b815260206004820152601460248201527310d15313c81d1c985b9cd9995c8819985a5b195960621b604482015290519081900360640190fd5b6040805184815290516001600160a01b0386169133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35060019392505050565b3390565b60006001600160a01b0383166117ba5760405162461bcd60e51b815260040180806020018281038252602a815260200180611a0a602a913960400191505060405180910390fd5b6110fd83836115b8565b6001600160a01b0381166118095760405162461bcd60e51b815260040180806020018281038252602681526020018061194f6026913960400191505060405180910390fd5b600080546040516001600160a01b038085169361010090930416917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b600081848411156118fe5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156118c35781810151838201526020016118ab565b50505050905090810190601f1680156118f05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b50505090039056fe4f6e6c79204c32546f4c314d6573736167655061737365722063616e2063616c6c2e6d696e7420617474656d7074656420746f2072657365727665642061646472657373203078304f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737354686973206d6574686f64206973206e6f206c6f6e67657220737570706f7274656420696e204c322e4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65727472616e736665722076616c75652065786365656465642062616c616e6365206f662073656e64657254686973206d6574686f64206973206e6f7420737570706f7274656420696e204c312e7472616e7366657220617474656d7074656420746f2072657365727665642061646472657373203078307472616e736665722076616c75652065786365656465642073656e646572277320616c6c6f77616e636520666f72207370656e646572a265627a7a72315820883737fc20fdb370fa6d2083191051192e78e14b9c9ed5974e58cd42d97124f264736f6c63430005110032
diff --git op-geth/contracts/celo/compiled/IFeeCurrencyDirectory.abi Celo/contracts/celo/compiled/IFeeCurrencyDirectory.abi
new file mode 100644
index 0000000000000000000000000000000000000000..90e071e994d161090a9347dfc7cf32ad918153d7
--- /dev/null
+++ Celo/contracts/celo/compiled/IFeeCurrencyDirectory.abi
@@ -0,0 +1,70 @@
+[
+ {
+ "type": "function",
+ "name": "getCurrencies",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address[]",
+ "internalType": "address[]"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getCurrencyConfig",
+ "inputs": [
+ {
+ "name": "token",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "tuple",
+ "internalType": "struct IFeeCurrencyDirectory.CurrencyConfig",
+ "components": [
+ {
+ "name": "oracle",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "intrinsicGas",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ]
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getExchangeRate",
+ "inputs": [
+ {
+ "name": "token",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "numerator",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "denominator",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ }
+]
diff --git op-geth/contracts/celo/compiled/MockOracle.abi Celo/contracts/celo/compiled/MockOracle.abi
new file mode 100644
index 0000000000000000000000000000000000000000..76bb6049a808f525e8460cd3ee1547e64de327c4
--- /dev/null
+++ Celo/contracts/celo/compiled/MockOracle.abi
@@ -0,0 +1,49 @@
+[
+ {
+ "type": "function",
+ "name": "getExchangeRate",
+ "inputs": [
+ {
+ "name": "_token",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "setExchangeRate",
+ "inputs": [
+ {
+ "name": "_token",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "_numerator",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "_denominator",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ }
+]
diff --git op-geth/contracts/celo/compiled/MockOracle.bin-runtime Celo/contracts/celo/compiled/MockOracle.bin-runtime
new file mode 100644
index 0000000000000000000000000000000000000000..a82a67d31ce2048d3b6579467d5fbc8ec83df59f
--- /dev/null
+++ Celo/contracts/celo/compiled/MockOracle.bin-runtime
@@ -0,0 +1 @@
+0x608060405234801561001057600080fd5b50600436106100365760003560e01c806358a5514f1461003b578063efb7601d1461007a575b600080fd5b61007861004936600461012d565b60009190915560015542600255600380546001600160a01b0319166001600160a01b0392909216919091179055565b005b61008d610088366004610160565b6100a6565b6040805192835260208301919091520160405180910390f35b60035460009081906001600160a01b038481169116146101025760405162461bcd60e51b8152602060048201526013602482015272151bdad95b881b9bdd081cdd5c1c1bdc9d1959606a1b604482015260640160405180910390fd5b60005460015491509150915091565b80356001600160a01b038116811461012857600080fd5b919050565b60008060006060848603121561014257600080fd5b61014b84610111565b95602085013595506040909401359392505050565b60006020828403121561017257600080fd5b61017b82610111565b939250505056fea2646970667358221220532d5a8180e3477753af960cd2ec6ffab9b57df9b867e656e78ca4ec2164930664736f6c63430008130033
diff --git op-geth/contracts/celo/compiled/update.sh Celo/contracts/celo/compiled/update.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d0f32423e162f3c0a479ef29dd41472c8566b148
--- /dev/null
+++ Celo/contracts/celo/compiled/update.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+SCRIPT_DIR=$(readlink -f "$(dirname "$0")")
+
+CONTRACTS_DIR=${CELO_OPTIMISM_REPO:-~/optimism}/packages/contracts-bedrock
+forge build --root "$CONTRACTS_DIR"
+
+for contract in FeeCurrency
+do
+ contract_json="$CONTRACTS_DIR/forge-artifacts/$contract.sol/$contract.json"
+ jq .abi "$contract_json" > "$SCRIPT_DIR/$contract.abi"
+ jq .deployedBytecode.object -r "$contract_json" > "$SCRIPT_DIR/$contract.bin-runtime"
+done
+
+CONTRACTS_DIR=${CELO_MONOREPO:-~/celo-monorepo}/packages/protocol
+forge build --root "$CONTRACTS_DIR"
+
+for contract in GoldToken FeeCurrencyDirectory IFeeCurrencyDirectory MockOracle
+do
+ contract_json="$CONTRACTS_DIR/out/$contract.sol/$contract.json"
+ jq .abi "$contract_json" > "$SCRIPT_DIR/$contract.abi"
+ jq .deployedBytecode.object -r "$contract_json" > "$SCRIPT_DIR/$contract.bin-runtime"
+done
+
+# We only need the abi for the interface (IFeeCurrencyDirectory) and the
+# bytecode for the implementation (FeeCurrencyDirectory), so let's delete the other.
+rm "$SCRIPT_DIR/IFeeCurrencyDirectory.bin-runtime" "$SCRIPT_DIR/FeeCurrencyDirectory.abi"
diff --git op-geth/core/celo_genesis.go Celo/core/celo_genesis.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e37f55b8f23a9dee0635c3b611e206c9beb193a
--- /dev/null
+++ Celo/core/celo_genesis.go
@@ -0,0 +1,169 @@
+package core
+
+import (
+ "encoding/hex"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/contracts/addresses"
+ "github.com/ethereum/go-ethereum/contracts/celo"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+// Decode 0x prefixed hex string from file (including trailing newline)
+func DecodeHex(hexbytes []byte) ([]byte, error) {
+ // Strip 0x prefix and trailing newline
+ hexbytes = hexbytes[2 : len(hexbytes)-1] // strip 0x prefix
+
+ // Decode hex string
+ bytes := make([]byte, hex.DecodedLen(len(hexbytes)))
+ _, err := hex.Decode(bytes, hexbytes)
+ if err != nil {
+ return nil, fmt.Errorf("DecodeHex: %w", err)
+ }
+
+ return bytes, nil
+}
+
+// Calculate address in evm mapping: keccak(key ++ mapping_slot)
+func CalcMapAddr(slot common.Hash, key common.Hash) common.Hash {
+ return crypto.Keccak256Hash(append(key.Bytes(), slot.Bytes()...))
+}
+
+// Increase a hash value by `i`, used for addresses in 32byte fields
+func incHash(addr common.Hash, i int64) common.Hash {
+ return common.BigToHash(new(big.Int).Add(addr.Big(), big.NewInt(i)))
+}
+
+var (
+ DevPrivateKey, _ = crypto.HexToECDSA("2771aff413cac48d9f8c114fabddd9195a2129f3c2c436caa07e27bb7f58ead5")
+ DevAddr = common.BytesToAddress(DevAddr32.Bytes())
+ DevAddr32 = common.HexToHash("0x42cf1bbc38BaAA3c4898ce8790e21eD2738c6A4a")
+
+ DevFeeCurrencyAddr = common.HexToAddress("0x000000000000000000000000000000000000ce16") // worth half as much as native CELO
+ DevFeeCurrencyAddr2 = common.HexToAddress("0x000000000000000000000000000000000000ce17") // worth twice as much as native CELO
+ DevBalance, _ = new(big.Int).SetString("100000000000000000000", 10)
+ rateNumerator, _ = new(big.Int).SetString("2000000000000000000000000", 10)
+ rateNumerator2, _ = new(big.Int).SetString("500000000000000000000000", 10)
+ rateDenominator, _ = new(big.Int).SetString("1000000000000000000000000", 10)
+ mockOracleAddr = common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0001")
+ mockOracleAddr2 = common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002")
+ mockOracleAddr3 = common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0003")
+ FaucetAddr = common.HexToAddress("0xfcf982bb4015852e706100b14e21f947a5bb718e")
+ FeeCurrencyIntrinsicGas = uint64(50000)
+)
+
+func CeloGenesisAccounts(fundedAddr common.Address) GenesisAlloc {
+ // Initialize Bytecodes
+ celoTokenBytecode, err := DecodeHex(celo.CeloTokenBytecodeRaw)
+ if err != nil {
+ panic(err)
+ }
+ feeCurrencyBytecode, err := DecodeHex(celo.FeeCurrencyBytecodeRaw)
+ if err != nil {
+ panic(err)
+ }
+ feeCurrencyDirectoryBytecode, err := DecodeHex(celo.FeeCurrencyDirectoryBytecodeRaw)
+ if err != nil {
+ panic(err)
+ }
+ mockOracleBytecode, err := DecodeHex(celo.MockOracleBytecodeRaw)
+ if err != nil {
+ panic(err)
+ }
+
+ var devBalance32 common.Hash
+ DevBalance.FillBytes(devBalance32[:])
+
+ arrayAtSlot2 := crypto.Keccak256Hash(common.HexToHash("0x2").Bytes())
+
+ faucetBalance, ok := new(big.Int).SetString("500000000000000000000000000", 10) // 500M
+ if !ok {
+ panic("Could not set faucet balance!")
+ }
+ genesisAccounts := map[common.Address]GenesisAccount{
+ addresses.MainnetAddresses.CeloToken: {
+ Code: celoTokenBytecode,
+ Balance: big.NewInt(0),
+ },
+ DevFeeCurrencyAddr: {
+ Code: feeCurrencyBytecode,
+ Balance: big.NewInt(0),
+ Storage: map[common.Hash]common.Hash{
+ CalcMapAddr(common.HexToHash("0x0"), DevAddr32): devBalance32, // _balances[DevAddr]
+ CalcMapAddr(common.HexToHash("0x0"), common.BytesToHash(fundedAddr.Bytes())): devBalance32, // _balances[fund]
+ common.HexToHash("0x2"): devBalance32, // _totalSupply
+ },
+ },
+ DevFeeCurrencyAddr2: {
+ Code: feeCurrencyBytecode,
+ Balance: big.NewInt(0),
+ Storage: map[common.Hash]common.Hash{
+ CalcMapAddr(common.HexToHash("0x0"), DevAddr32): devBalance32, // _balances[DevAddr]
+ CalcMapAddr(common.HexToHash("0x0"), common.BytesToHash(fundedAddr.Bytes())): devBalance32, // _balances[fund]
+ common.HexToHash("0x2"): devBalance32, // _totalSupply
+ },
+ },
+ mockOracleAddr: {
+ Code: mockOracleBytecode,
+ Balance: big.NewInt(0),
+ Storage: map[common.Hash]common.Hash{
+ common.HexToHash("0x0"): common.BigToHash(rateNumerator),
+ common.HexToHash("0x1"): common.BigToHash(rateDenominator),
+ common.HexToHash("0x3"): common.BytesToHash(DevFeeCurrencyAddr.Bytes()),
+ },
+ },
+ mockOracleAddr2: {
+ Code: mockOracleBytecode,
+ Balance: big.NewInt(0),
+ Storage: map[common.Hash]common.Hash{
+ common.HexToHash("0x0"): common.BigToHash(rateNumerator2),
+ common.HexToHash("0x1"): common.BigToHash(rateDenominator),
+ common.HexToHash("0x3"): common.BytesToHash(DevFeeCurrencyAddr2.Bytes()),
+ },
+ },
+ mockOracleAddr3: {
+ Code: mockOracleBytecode,
+ Balance: big.NewInt(0),
+ // This oracle is available for tests of contracts outside the celo_genesis, so no initialization is done at this point
+ },
+ DevAddr: {
+ Balance: DevBalance,
+ },
+ FaucetAddr: {
+ Balance: faucetBalance,
+ },
+ fundedAddr: {
+ Balance: DevBalance,
+ },
+ }
+
+ // FeeCurrencyDirectory
+ devAddrOffset1 := common.Hash{}
+ copy(devAddrOffset1[11:], DevAddr.Bytes())
+ feeCurrencyDirectoryStorage := map[common.Hash]common.Hash{
+ // owner, slot 0 offset 1
+ common.HexToHash("0x0"): devAddrOffset1,
+ // add entries to currencyList at slot 2
+ common.HexToHash("0x2"): common.HexToHash("0x2"), // array length 2
+ arrayAtSlot2: common.BytesToHash(DevFeeCurrencyAddr.Bytes()), // FeeCurrency
+ incHash(arrayAtSlot2, 1): common.BytesToHash(DevFeeCurrencyAddr2.Bytes()), // FeeCurrency2
+ }
+ // add entries to currencyConfig mapping
+ addFeeCurrencyToStorage(DevFeeCurrencyAddr, mockOracleAddr, feeCurrencyDirectoryStorage)
+ addFeeCurrencyToStorage(DevFeeCurrencyAddr2, mockOracleAddr2, feeCurrencyDirectoryStorage)
+ genesisAccounts[addresses.MainnetAddresses.FeeCurrencyDirectory] = GenesisAccount{
+ Code: feeCurrencyDirectoryBytecode,
+ Balance: big.NewInt(0),
+ Storage: feeCurrencyDirectoryStorage,
+ }
+
+ return genesisAccounts
+}
+
+func addFeeCurrencyToStorage(feeCurrencyAddr common.Address, oracleAddr common.Address, storage map[common.Hash]common.Hash) {
+ structStart := CalcMapAddr(common.HexToHash("0x1"), common.BytesToHash(feeCurrencyAddr.Bytes()))
+ storage[structStart] = common.BytesToHash(oracleAddr.Bytes()) // oracle
+ storage[incHash(structStart, 1)] = common.BigToHash(big.NewInt(int64(FeeCurrencyIntrinsicGas))) // intrinsicGas
+}
Chain config
+92
-4
diff --git op-geth/core/genesis.go Celo/core/genesis.go
index f8032507337e24a6fcd32d8b0eba8c9edf3f6485..27ca1c0241e086edcb8146e986d7e977fa480235 100644
--- op-geth/core/genesis.go
+++ Celo/core/genesis.go
@@ -61,8 +61,8 @@ Config *params.ChainConfig `json:"config"`
Nonce uint64 `json:"nonce"`
Timestamp uint64 `json:"timestamp"`
ExtraData []byte `json:"extraData"`
- GasLimit uint64 `json:"gasLimit" gencodec:"required"`
- Difficulty *big.Int `json:"difficulty" gencodec:"required"`
+ GasLimit uint64 `json:"gasLimit"`
+ Difficulty *big.Int `json:"difficulty"`
Mixhash common.Hash `json:"mixHash"`
Coinbase common.Address `json:"coinbase"`
Alloc types.GenesisAlloc `json:"alloc" gencodec:"required"`
@@ -258,6 +258,8 @@ OverrideOptimismGranite *uint64
OverrideOptimismHolocene *uint64
OverrideOptimismInterop *uint64
ApplySuperchainUpgrades bool
+ // celo
+ OverrideOptimismCel2 *uint64
}
// SetupGenesisBlock writes or updates the genesis block in db.
@@ -324,6 +326,9 @@ config.HoloceneTime = overrides.OverrideOptimismHolocene
}
if overrides != nil && overrides.OverrideOptimismInterop != nil {
config.InteropTime = overrides.OverrideOptimismInterop
+ }
+ if overrides != nil && overrides.OverrideOptimismCel2 != nil {
+ config.Cel2Time = overrides.OverrideOptimismCel2
}
}
}
@@ -512,6 +517,9 @@ head.GasLimit = params.GenesisGasLimit
}
if g.Difficulty == nil && g.Mixhash == (common.Hash{}) {
head.Difficulty = params.GenesisDifficulty
+ } else if g.Difficulty == nil {
+ // In the case of migrated chains we ensure a zero rather than nil difficulty.
+ head.Difficulty = new(big.Int)
}
if g.Config != nil && g.Config.IsLondon(common.Big0) {
if g.BaseFee != nil {
@@ -681,6 +689,12 @@ }
if faucet != nil {
genesis.Alloc[*faucet] = types.Account{Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))}
}
+
+ // Add state from celoGenesisAccounts
+ for addr, data := range CeloGenesisAccounts(common.HexToAddress("0x2")) {
+ genesis.Alloc[addr] = data
+ }
+
return genesis
}
diff --git op-geth/params/config.go Celo/params/config.go
index 04cc43a8403775a9e9ba9ddf7c537535c437ced2..eea0f167cadeb77af39d3b1b9ed29dbf5417aa50 100644
--- op-geth/params/config.go
+++ Celo/params/config.go
@@ -164,6 +164,8 @@ ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
ShanghaiTime: newUint64(0),
CancunTime: newUint64(0),
+ Cel2Time: newUint64(0),
+ GingerbreadBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
}
@@ -222,6 +224,39 @@ ShanghaiTime: nil,
CancunTime: nil,
PragueTime: nil,
VerkleTime: nil,
+ GingerbreadBlock: big.NewInt(0),
+ Cel2Time: newUint64(0),
+ TerminalTotalDifficulty: nil,
+ TerminalTotalDifficultyPassed: false,
+ Ethash: new(EthashConfig),
+ Clique: nil,
+ }
+
+ // TestChainConfigNoCel2 contains every protocol change (EIPs) introduced
+ // and accepted by the Ethereum core developers for testing purposes.
+ TestChainConfigNoCel2 = &ChainConfig{
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: nil,
+ DAOForkSupport: false,
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ GrayGlacierBlock: big.NewInt(0),
+ MergeNetsplitBlock: nil,
+ ShanghaiTime: nil,
+ CancunTime: nil,
+ PragueTime: nil,
+ VerkleTime: nil,
+ Cel2Time: nil,
TerminalTotalDifficulty: nil,
TerminalTotalDifficultyPassed: false,
Ethash: new(EthashConfig),
@@ -248,6 +283,7 @@ LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0),
+ GingerbreadBlock: big.NewInt(0),
ShanghaiTime: newUint64(0),
CancunTime: newUint64(0),
PragueTime: nil,
@@ -343,6 +379,8 @@ CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
+ // Note that the bedrock block is also the first block of the celo L2, because it must be set for the celo l2 to
+ // function correctly and it can't be set before we migrate to the L2.
BedrockBlock *big.Int `json:"bedrockBlock,omitempty"` // Bedrock switch block (nil = no fork, 0 = already on optimism bedrock)
RegolithTime *uint64 `json:"regolithTime,omitempty"` // Regolith switch time (nil = no fork, 0 = already on optimism regolith)
CanyonTime *uint64 `json:"canyonTime,omitempty"` // Canyon switch time (nil = no fork, 0 = already on optimism canyon)
@@ -354,6 +392,9 @@ HoloceneTime *uint64 `json:"holoceneTime,omitempty"` // Holocene switch time (nil = no fork, 0 = already on Optimism Holocene)
InteropTime *uint64 `json:"interopTime,omitempty"` // Interop switch time (nil = no fork, 0 = already on optimism interop)
+ Cel2Time *uint64 `json:"cel2Time,omitempty"` // Cel2 switch time (nil = no fork, 0 = already on optimism cel2)
+ GingerbreadBlock *big.Int `json:"gingerbreadBlock,omitempty"` // Gingerbread switch block (nil = no fork, 0 = already activated)
+
// TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade.
TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"`
@@ -373,6 +414,8 @@ Clique *CliqueConfig `json:"clique,omitempty"`
// Optimism config, nil if not active
Optimism *OptimismConfig `json:"optimism,omitempty"`
+ // Celo config, nil if not active
+ Celo *CeloConfig `json:"celo,omitempty"`
}
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
@@ -403,7 +446,21 @@ }
// String implements the stringer interface, returning the optimism fee config details.
func (o *OptimismConfig) String() string {
- return "optimism"
+ denominatorCanyonStr := "nil"
+ if o.EIP1559DenominatorCanyon != nil {
+ denominatorCanyonStr = fmt.Sprintf("%d", *o.EIP1559DenominatorCanyon)
+ }
+ return fmt.Sprintf("optimism(eip1559Elasticity: %d, eip1559Denominator: %d, eip1559DenominatorCanyon: %s)",
+ o.EIP1559Elasticity, o.EIP1559Denominator, denominatorCanyonStr)
+}
+
+type CeloConfig struct {
+ EIP1559BaseFeeFloor uint64 `json:"eip1559BaseFeeFloor"`
+}
+
+// String implements the stringer interface, returning the celo config details.
+func (o *CeloConfig) String() string {
+ return fmt.Sprintf("celo(eip1559BaseFeeFloor: %d)", o.EIP1559BaseFeeFloor)
}
// Description returns a human-readable description of ChainConfig.
@@ -419,6 +476,8 @@ banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network)
switch {
case c.Optimism != nil:
banner += "Consensus: Optimism\n"
+ banner += fmt.Sprintf(" - %s\n", c.Optimism)
+ banner += fmt.Sprintf(" - %s\n", c.Celo)
case c.Ethash != nil:
if c.TerminalTotalDifficulty == nil {
banner += "Consensus: Ethash (proof-of-work)\n"
@@ -517,6 +576,9 @@ banner += fmt.Sprintf(" - Holocene: @%-10v\n", *c.HoloceneTime)
}
if c.InteropTime != nil {
banner += fmt.Sprintf(" - Interop: @%-10v\n", *c.InteropTime)
+ }
+ if c.Cel2Time != nil {
+ banner += fmt.Sprintf(" - Cel2: @%-10v\n", *c.Cel2Time)
}
return banner
}
@@ -657,6 +719,15 @@ }
func (c *ChainConfig) IsInterop(time uint64) bool {
return isTimestampForked(c.InteropTime, time)
+}
+
+func (c *ChainConfig) IsCel2(time uint64) bool {
+ return isTimestampForked(c.Cel2Time, time)
+}
+
+// IsGingerbread returns whether num represents a block number after the Gingerbread fork
+func (c *ChainConfig) IsGingerbread(num *big.Int) bool {
+ return isBlockForked(c.GingerbreadBlock, num)
}
// IsOptimism returns whether the node is an optimism node or not.
@@ -1078,6 +1149,7 @@ IsVerkle bool
IsOptimismBedrock, IsOptimismRegolith bool
IsOptimismCanyon, IsOptimismFjord bool
IsOptimismGranite, IsOptimismHolocene bool
+ IsCel2 bool
}
// Rules ensures c's ChainID is not nil.
@@ -1115,5 +1187,7 @@ IsOptimismCanyon: isMerge && c.IsOptimismCanyon(timestamp),
IsOptimismFjord: isMerge && c.IsOptimismFjord(timestamp),
IsOptimismGranite: isMerge && c.IsOptimismGranite(timestamp),
IsOptimismHolocene: isMerge && c.IsOptimismHolocene(timestamp),
+ // Celo
+ IsCel2: c.IsCel2(timestamp),
}
}
diff --git op-geth/params/protocol_params.go Celo/params/protocol_params.go
index 107a9fecfcb245c0c5f223ccc8ef0f9cb2e93ff8..16afc3b5ef147d9a768622447a59ae254c0948a3 100644
--- op-geth/params/protocol_params.go
+++ Celo/params/protocol_params.go
@@ -136,7 +136,7 @@ DefaultBaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks.
DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks.
- MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
+ MaxCodeSize = 65536 // Maximum bytecode to permit for a contract
MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions
// Precompiled contract gas prices
E2E tests
+3112
-0
diff --git op-geth/e2e_test/.gitignore Celo/e2e_test/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d93e90bc6936c69f4976f29030cd041295ba09cb
--- /dev/null
+++ Celo/e2e_test/.gitignore
@@ -0,0 +1,3 @@
+cache
+out
+geth.log
diff --git op-geth/e2e_test/debug-fee-currency/DebugFeeCurrency.sol Celo/e2e_test/debug-fee-currency/DebugFeeCurrency.sol
new file mode 100644
index 0000000000000000000000000000000000000000..7f269dd5f20b56904ec98212b8ec93b212c0d83b
--- /dev/null
+++ Celo/e2e_test/debug-fee-currency/DebugFeeCurrency.sol
@@ -0,0 +1,770 @@
+pragma solidity ^0.8.13;
+
+// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
+
+// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
+
+/**
+ * @dev Interface of the ERC20 standard as defined in the EIP.
+ */
+interface IERC20 {
+ /**
+ * @dev Emitted when `value` tokens are moved from one account (`from`) to
+ * another (`to`).
+ *
+ * Note that `value` may be zero.
+ */
+ event Transfer(address indexed from, address indexed to, uint256 value);
+
+ /**
+ * @dev Emitted when the allowance of a `spender` for an `owner` is set by
+ * a call to {approve}. `value` is the new allowance.
+ */
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+
+ /**
+ * @dev Returns the value of tokens in existence.
+ */
+ function totalSupply() external view returns (uint256);
+
+ /**
+ * @dev Returns the value of tokens owned by `account`.
+ */
+ function balanceOf(address account) external view returns (uint256);
+
+ /**
+ * @dev Moves a `value` amount of tokens from the caller's account to `to`.
+ *
+ * Returns a boolean value indicating whether the operation succeeded.
+ *
+ * Emits a {Transfer} event.
+ */
+ function transfer(address to, uint256 value) external returns (bool);
+
+ /**
+ * @dev Returns the remaining number of tokens that `spender` will be
+ * allowed to spend on behalf of `owner` through {transferFrom}. This is
+ * zero by default.
+ *
+ * This value changes when {approve} or {transferFrom} are called.
+ */
+ function allowance(address owner, address spender) external view returns (uint256);
+
+ /**
+ * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
+ * caller's tokens.
+ *
+ * Returns a boolean value indicating whether the operation succeeded.
+ *
+ * IMPORTANT: Beware that changing an allowance with this method brings the risk
+ * that someone may use both the old and the new allowance by unfortunate
+ * transaction ordering. One possible solution to mitigate this race
+ * condition is to first reduce the spender's allowance to 0 and set the
+ * desired value afterwards:
+ * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
+ *
+ * Emits an {Approval} event.
+ */
+ function approve(address spender, uint256 value) external returns (bool);
+
+ /**
+ * @dev Moves a `value` amount of tokens from `from` to `to` using the
+ * allowance mechanism. `value` is then deducted from the caller's
+ * allowance.
+ *
+ * Returns a boolean value indicating whether the operation succeeded.
+ *
+ * Emits a {Transfer} event.
+ */
+ function transferFrom(address from, address to, uint256 value) external returns (bool);
+}
+
+// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
+
+/**
+ * @dev Interface for the optional metadata functions from the ERC20 standard.
+ */
+interface IERC20Metadata is IERC20 {
+ /**
+ * @dev Returns the name of the token.
+ */
+ function name() external view returns (string memory);
+
+ /**
+ * @dev Returns the symbol of the token.
+ */
+ function symbol() external view returns (string memory);
+
+ /**
+ * @dev Returns the decimals places of the token.
+ */
+ function decimals() external view returns (uint8);
+}
+
+// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
+
+/**
+ * @dev Provides information about the current execution context, including the
+ * sender of the transaction and its data. While these are generally available
+ * via msg.sender and msg.data, they should not be accessed in such a direct
+ * manner, since when dealing with meta-transactions the account sending and
+ * paying for execution may not be the actual sender (as far as an application
+ * is concerned).
+ *
+ * This contract is only required for intermediate, library-like contracts.
+ */
+abstract contract Context {
+ function _msgSender() internal view virtual returns (address) {
+ return msg.sender;
+ }
+
+ function _msgData() internal view virtual returns (bytes calldata) {
+ return msg.data;
+ }
+
+ function _contextSuffixLength() internal view virtual returns (uint256) {
+ return 0;
+ }
+}
+
+// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
+
+/**
+ * @dev Standard ERC20 Errors
+ * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
+ */
+interface IERC20Errors {
+ /**
+ * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
+ * @param sender Address whose tokens are being transferred.
+ * @param balance Current balance for the interacting account.
+ * @param needed Minimum amount required to perform a transfer.
+ */
+ error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
+
+ /**
+ * @dev Indicates a failure with the token `sender`. Used in transfers.
+ * @param sender Address whose tokens are being transferred.
+ */
+ error ERC20InvalidSender(address sender);
+
+ /**
+ * @dev Indicates a failure with the token `receiver`. Used in transfers.
+ * @param receiver Address to which tokens are being transferred.
+ */
+ error ERC20InvalidReceiver(address receiver);
+
+ /**
+ * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
+ * @param spender Address that may be allowed to operate on tokens without being their owner.
+ * @param allowance Amount of tokens a `spender` is allowed to operate with.
+ * @param needed Minimum amount required to perform a transfer.
+ */
+ error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
+
+ /**
+ * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
+ * @param approver Address initiating an approval operation.
+ */
+ error ERC20InvalidApprover(address approver);
+
+ /**
+ * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
+ * @param spender Address that may be allowed to operate on tokens without being their owner.
+ */
+ error ERC20InvalidSpender(address spender);
+}
+
+/**
+ * @dev Standard ERC721 Errors
+ * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
+ */
+interface IERC721Errors {
+ /**
+ * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
+ * Used in balance queries.
+ * @param owner Address of the current owner of a token.
+ */
+ error ERC721InvalidOwner(address owner);
+
+ /**
+ * @dev Indicates a `tokenId` whose `owner` is the zero address.
+ * @param tokenId Identifier number of a token.
+ */
+ error ERC721NonexistentToken(uint256 tokenId);
+
+ /**
+ * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
+ * @param sender Address whose tokens are being transferred.
+ * @param tokenId Identifier number of a token.
+ * @param owner Address of the current owner of a token.
+ */
+ error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
+
+ /**
+ * @dev Indicates a failure with the token `sender`. Used in transfers.
+ * @param sender Address whose tokens are being transferred.
+ */
+ error ERC721InvalidSender(address sender);
+
+ /**
+ * @dev Indicates a failure with the token `receiver`. Used in transfers.
+ * @param receiver Address to which tokens are being transferred.
+ */
+ error ERC721InvalidReceiver(address receiver);
+
+ /**
+ * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
+ * @param operator Address that may be allowed to operate on tokens without being their owner.
+ * @param tokenId Identifier number of a token.
+ */
+ error ERC721InsufficientApproval(address operator, uint256 tokenId);
+
+ /**
+ * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
+ * @param approver Address initiating an approval operation.
+ */
+ error ERC721InvalidApprover(address approver);
+
+ /**
+ * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
+ * @param operator Address that may be allowed to operate on tokens without being their owner.
+ */
+ error ERC721InvalidOperator(address operator);
+}
+
+/**
+ * @dev Standard ERC1155 Errors
+ * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
+ */
+interface IERC1155Errors {
+ /**
+ * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
+ * @param sender Address whose tokens are being transferred.
+ * @param balance Current balance for the interacting account.
+ * @param needed Minimum amount required to perform a transfer.
+ * @param tokenId Identifier number of a token.
+ */
+ error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
+
+ /**
+ * @dev Indicates a failure with the token `sender`. Used in transfers.
+ * @param sender Address whose tokens are being transferred.
+ */
+ error ERC1155InvalidSender(address sender);
+
+ /**
+ * @dev Indicates a failure with the token `receiver`. Used in transfers.
+ * @param receiver Address to which tokens are being transferred.
+ */
+ error ERC1155InvalidReceiver(address receiver);
+
+ /**
+ * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
+ * @param operator Address that may be allowed to operate on tokens without being their owner.
+ * @param owner Address of the current owner of a token.
+ */
+ error ERC1155MissingApprovalForAll(address operator, address owner);
+
+ /**
+ * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
+ * @param approver Address initiating an approval operation.
+ */
+ error ERC1155InvalidApprover(address approver);
+
+ /**
+ * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
+ * @param operator Address that may be allowed to operate on tokens without being their owner.
+ */
+ error ERC1155InvalidOperator(address operator);
+
+ /**
+ * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
+ * Used in batch transfers.
+ * @param idsLength Length of the array of token identifiers
+ * @param valuesLength Length of the array of token amounts
+ */
+ error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
+}
+
+/**
+ * @dev Implementation of the {IERC20} interface.
+ *
+ * This implementation is agnostic to the way tokens are created. This means
+ * that a supply mechanism has to be added in a derived contract using {_mint}.
+ *
+ * TIP: For a detailed writeup see our guide
+ * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
+ * to implement supply mechanisms].
+ *
+ * The default value of {decimals} is 18. To change this, you should override
+ * this function so it returns a different value.
+ *
+ * We have followed general OpenZeppelin Contracts guidelines: functions revert
+ * instead returning `false` on failure. This behavior is nonetheless
+ * conventional and does not conflict with the expectations of ERC20
+ * applications.
+ *
+ * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
+ * This allows applications to reconstruct the allowance for all accounts just
+ * by listening to said events. Other implementations of the EIP may not emit
+ * these events, as it isn't required by the specification.
+ */
+abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
+ mapping(address account => uint256) private _balances;
+
+ mapping(address account => mapping(address spender => uint256)) private _allowances;
+
+ uint256 private _totalSupply;
+
+ string private _name;
+ string private _symbol;
+
+ /**
+ * @dev Sets the values for {name} and {symbol}.
+ *
+ * All two of these values are immutable: they can only be set once during
+ * construction.
+ */
+ constructor(string memory name_, string memory symbol_) {
+ _name = name_;
+ _symbol = symbol_;
+ }
+
+ /**
+ * @dev Returns the name of the token.
+ */
+ function name() public view virtual returns (string memory) {
+ return _name;
+ }
+
+ /**
+ * @dev Returns the symbol of the token, usually a shorter version of the
+ * name.
+ */
+ function symbol() public view virtual returns (string memory) {
+ return _symbol;
+ }
+
+ /**
+ * @dev Returns the number of decimals used to get its user representation.
+ * For example, if `decimals` equals `2`, a balance of `505` tokens should
+ * be displayed to a user as `5.05` (`505 / 10 ** 2`).
+ *
+ * Tokens usually opt for a value of 18, imitating the relationship between
+ * Ether and Wei. This is the default value returned by this function, unless
+ * it's overridden.
+ *
+ * NOTE: This information is only used for _display_ purposes: it in
+ * no way affects any of the arithmetic of the contract, including
+ * {IERC20-balanceOf} and {IERC20-transfer}.
+ */
+ function decimals() public view virtual returns (uint8) {
+ return 18;
+ }
+
+ /**
+ * @dev See {IERC20-totalSupply}.
+ */
+ function totalSupply() public view virtual returns (uint256) {
+ return _totalSupply;
+ }
+
+ /**
+ * @dev See {IERC20-balanceOf}.
+ */
+ function balanceOf(address account) public view virtual returns (uint256) {
+ return _balances[account];
+ }
+
+ /**
+ * @dev See {IERC20-transfer}.
+ *
+ * Requirements:
+ *
+ * - `to` cannot be the zero address.
+ * - the caller must have a balance of at least `value`.
+ */
+ function transfer(address to, uint256 value) public virtual returns (bool) {
+ address owner = _msgSender();
+ _transfer(owner, to, value);
+ return true;
+ }
+
+ /**
+ * @dev See {IERC20-allowance}.
+ */
+ function allowance(address owner, address spender) public view virtual returns (uint256) {
+ return _allowances[owner][spender];
+ }
+
+ /**
+ * @dev See {IERC20-approve}.
+ *
+ * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
+ * `transferFrom`. This is semantically equivalent to an infinite approval.
+ *
+ * Requirements:
+ *
+ * - `spender` cannot be the zero address.
+ */
+ function approve(address spender, uint256 value) public virtual returns (bool) {
+ address owner = _msgSender();
+ _approve(owner, spender, value);
+ return true;
+ }
+
+ /**
+ * @dev See {IERC20-transferFrom}.
+ *
+ * Emits an {Approval} event indicating the updated allowance. This is not
+ * required by the EIP. See the note at the beginning of {ERC20}.
+ *
+ * NOTE: Does not update the allowance if the current allowance
+ * is the maximum `uint256`.
+ *
+ * Requirements:
+ *
+ * - `from` and `to` cannot be the zero address.
+ * - `from` must have a balance of at least `value`.
+ * - the caller must have allowance for ``from``'s tokens of at least
+ * `value`.
+ */
+ function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
+ address spender = _msgSender();
+ _spendAllowance(from, spender, value);
+ _transfer(from, to, value);
+ return true;
+ }
+
+ /**
+ * @dev Moves a `value` amount of tokens from `from` to `to`.
+ *
+ * This internal function is equivalent to {transfer}, and can be used to
+ * e.g. implement automatic token fees, slashing mechanisms, etc.
+ *
+ * Emits a {Transfer} event.
+ *
+ * NOTE: This function is not virtual, {_update} should be overridden instead.
+ */
+ function _transfer(address from, address to, uint256 value) internal {
+ if (from == address(0)) {
+ revert ERC20InvalidSender(address(0));
+ }
+ if (to == address(0)) {
+ revert ERC20InvalidReceiver(address(0));
+ }
+ _update(from, to, value);
+ }
+
+ /**
+ * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
+ * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
+ * this function.
+ *
+ * Emits a {Transfer} event.
+ */
+ function _update(address from, address to, uint256 value) internal virtual {
+ if (from == address(0)) {
+ // Overflow check required: The rest of the code assumes that totalSupply never overflows
+ _totalSupply += value;
+ } else {
+ uint256 fromBalance = _balances[from];
+ if (fromBalance < value) {
+ revert ERC20InsufficientBalance(from, fromBalance, value);
+ }
+ unchecked {
+ // Overflow not possible: value <= fromBalance <= totalSupply.
+ _balances[from] = fromBalance - value;
+ }
+ }
+
+ if (to == address(0)) {
+ unchecked {
+ // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
+ _totalSupply -= value;
+ }
+ } else {
+ unchecked {
+ // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
+ _balances[to] += value;
+ }
+ }
+
+ emit Transfer(from, to, value);
+ }
+
+ /**
+ * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
+ * Relies on the `_update` mechanism
+ *
+ * Emits a {Transfer} event with `from` set to the zero address.
+ *
+ * NOTE: This function is not virtual, {_update} should be overridden instead.
+ */
+ function _mint(address account, uint256 value) internal {
+ if (account == address(0)) {
+ revert ERC20InvalidReceiver(address(0));
+ }
+ _update(address(0), account, value);
+ }
+
+ /**
+ * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
+ * Relies on the `_update` mechanism.
+ *
+ * Emits a {Transfer} event with `to` set to the zero address.
+ *
+ * NOTE: This function is not virtual, {_update} should be overridden instead
+ */
+ function _burn(address account, uint256 value) internal {
+ if (account == address(0)) {
+ revert ERC20InvalidSender(address(0));
+ }
+ _update(account, address(0), value);
+ }
+
+ /**
+ * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
+ *
+ * This internal function is equivalent to `approve`, and can be used to
+ * e.g. set automatic allowances for certain subsystems, etc.
+ *
+ * Emits an {Approval} event.
+ *
+ * Requirements:
+ *
+ * - `owner` cannot be the zero address.
+ * - `spender` cannot be the zero address.
+ *
+ * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
+ */
+ function _approve(address owner, address spender, uint256 value) internal {
+ _approve(owner, spender, value, true);
+ }
+
+ /**
+ * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
+ *
+ * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
+ * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
+ * `Approval` event during `transferFrom` operations.
+ *
+ * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
+ * true using the following override:
+ * ```
+ * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
+ * super._approve(owner, spender, value, true);
+ * }
+ * ```
+ *
+ * Requirements are the same as {_approve}.
+ */
+ function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
+ if (owner == address(0)) {
+ revert ERC20InvalidApprover(address(0));
+ }
+ if (spender == address(0)) {
+ revert ERC20InvalidSpender(address(0));
+ }
+ _allowances[owner][spender] = value;
+ if (emitEvent) {
+ emit Approval(owner, spender, value);
+ }
+ }
+
+ /**
+ * @dev Updates `owner` s allowance for `spender` based on spent `value`.
+ *
+ * Does not update the allowance value in case of infinite allowance.
+ * Revert if not enough allowance is available.
+ *
+ * Does not emit an {Approval} event.
+ */
+ function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
+ uint256 currentAllowance = allowance(owner, spender);
+ if (currentAllowance != type(uint256).max) {
+ if (currentAllowance < value) {
+ revert ERC20InsufficientAllowance(spender, currentAllowance, value);
+ }
+ unchecked {
+ _approve(owner, spender, currentAllowance - value, false);
+ }
+ }
+ }
+}
+
+/**
+ * @notice This interface should be implemented for tokens which are supposed to
+ * act as fee currencies on the Celo blockchain, meaning that they can be
+ * used to pay gas fees for CIP-64 transactions (and some older tx types).
+ * See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md
+ *
+ * @notice Before executing a tx with non-empty `feeCurrency` field, the fee
+ * currency's `debitGasFees` function is called to reserve the maximum
+ * amount of gas token that tx can spend. After the tx has been executed, the
+ * `creditGasFees` function is called to refund any unused gas and credit
+ * the spent fees to the appropriate recipients. Events which are emitted in
+ * these functions will show up for every tx using the token as a
+ * fee currency.
+ *
+ * @dev Requirements:
+ * - The functions will be called by the blockchain client with `msg.sender
+ * == address(0)`. If this condition is not met, the functions must
+ * revert to prevent malicious users from crediting their accounts directly.
+ * - `creditGasFees` must credit all specified amounts. If this is not
+ * possible the functions must revert to prevent inconsistencies between
+ * the debited and credited amounts.
+ *
+ * @dev Notes on compatibility:
+ * - There are two versions of `creditGasFees`: one for the current
+ * (2024-01-16) blockchain implementation and a more future-proof version
+ * that omits deprecated fields and accommodates potential new recipients
+ * that might become necessary on later blockchain implementations. Both
+ * versions should be implemented to increase compatibility.
+ */
+interface IFeeCurrency is IERC20 {
+ /// @notice Called before transaction execution to reserve the maximum amount of gas
+ /// that can be used by the transaction.
+ /// - The implementation must deduct `value` from `from`'s balance.
+ /// - Must revert if `msg.sender` is not the zero address.
+ function debitGasFees(address from, uint256 value) external;
+
+ /// @notice New function signature, will be used when all fee currencies have migrated.
+ /// Credited amounts include gas refund, base fee and tip. Future additions
+ /// may include L1 gas fee when Celo becomes and L2.
+ /// - The implementation must increase each `recipient`'s balance by corresponding `amount`.
+ /// - Must revert if `msg.sender` is not the zero address.
+ /// - Must revert if `recipients` and `amounts` have different lengths.
+ function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) external;
+
+ /// @notice Old function signature for backwards compatibility
+ /// - Must revert if `msg.sender` is not the zero address.
+ /// - `refundAmount` must be credited to `refundRecipient`
+ /// - `tipAmount` must be credited to `tipRecipient`
+ /// - `baseFeeAmount` must be credited to `baseFeeRecipient`
+ /// - `_gatewayFeeRecipient` and `_gatewayFeeAmount` only exist for backwards
+ /// compatibility reasons and will always be zero.
+ function creditGasFees(
+ address refundRecipient,
+ address tipRecipient,
+ address _gatewayFeeRecipient,
+ address baseFeeRecipient,
+ uint256 refundAmount,
+ uint256 tipAmount,
+ uint256 _gatewayFeeAmount,
+ uint256 baseFeeAmount
+ ) external;
+}
+
+contract FeeCurrency is ERC20, IFeeCurrency {
+ constructor(uint256 initialSupply) ERC20("ExampleFeeCurrency", "EFC") {
+ _mint(msg.sender, initialSupply);
+ }
+
+ modifier onlyVm() {
+ require(msg.sender == address(0), "Only VM can call");
+ _;
+ }
+
+ function debitGasFees(address from, uint256 value) external onlyVm {
+ _burn(from, value);
+ }
+
+ // New function signature, will be used when all fee currencies have migrated
+ function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) public onlyVm {
+ require(recipients.length == amounts.length, "Recipients and amounts must be the same length.");
+
+ for (uint256 i = 0; i < recipients.length; i++) {
+ _mint(recipients[i], amounts[i]);
+ }
+ }
+
+ // Old function signature for backwards compatibility
+ function creditGasFees(
+ address from,
+ address feeRecipient,
+ address, // gatewayFeeRecipient, unused
+ address communityFund,
+ uint256 refund,
+ uint256 tipTxFee,
+ uint256, // gatewayFee, unused
+ uint256 baseTxFee
+ ) public onlyVm {
+ // Calling the new creditGasFees would make sense here, but that is not
+ // possible due to its calldata arguments.
+ _mint(from, refund);
+ _mint(feeRecipient, tipTxFee);
+ _mint(communityFund, baseTxFee);
+ }
+}
+
+contract DebugFeeCurrency is ERC20, IFeeCurrency {
+ bool failOnDebit;
+ bool failOnCredit;
+ bool highGasOnCredit;
+ mapping(uint256 => uint256) private _dummyMap;
+
+ constructor(uint256 initialSupply, bool _failOnDebit, bool _failOnCredit, bool _highGasOnCredit) ERC20("DebugFeeCurrency", "DFC") {
+ _mint(msg.sender, initialSupply);
+ failOnDebit = _failOnDebit;
+ failOnCredit = _failOnCredit;
+ highGasOnCredit = _highGasOnCredit;
+ }
+
+ modifier onlyVm() {
+ require(msg.sender == address(0), "Only VM can call");
+ _;
+ }
+
+ function debitGasFees(address from, uint256 value) external onlyVm {
+ require(!failOnDebit, "This DebugFeeCurrency always fails in debitGasFees!");
+ _burn(from, value);
+ }
+
+ // New function signature, will be used when all fee currencies have migrated
+ function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) public onlyVm {
+ require(!failOnCredit, "This DebugFeeCurrency always fails in (new) creditGasFees!");
+ require(recipients.length == amounts.length, "Recipients and amounts must be the same length.");
+
+ for (uint256 i = 0; i < recipients.length; i++) {
+ _mint(recipients[i], amounts[i]);
+ }
+
+ if (highGasOnCredit){
+ induceHighGasCost();
+ }
+ }
+
+ function induceHighGasCost() internal view {
+ // SLOAD for non existing touched_storage_slots
+ // 2100 * 3000 gas = 630.0000
+ for (uint256 i = 0; i < 3000; i++) {
+ _dummyMap[i];
+ }
+ }
+
+ // Old function signature for backwards compatibility
+ function creditGasFees(
+ address from,
+ address feeRecipient,
+ address, // gatewayFeeRecipient, unused
+ address communityFund,
+ uint256 refund,
+ uint256 tipTxFee,
+ uint256, // gatewayFee, unused
+ uint256 baseTxFee
+ ) public onlyVm {
+ require(!failOnCredit, "This DebugFeeCurrency always fails in (old) creditGasFees!");
+ // Calling the new creditGasFees would make sense here, but that is not
+ // possible due to its calldata arguments.
+ _mint(from, refund);
+ _mint(feeRecipient, tipTxFee);
+ _mint(communityFund, baseTxFee);
+
+ if (highGasOnCredit){
+ induceHighGasCost();
+ }
+ }
+}
+
diff --git op-geth/e2e_test/debug-fee-currency/deploy_and_send_tx.sh Celo/e2e_test/debug-fee-currency/deploy_and_send_tx.sh
new file mode 100755
index 0000000000000000000000000000000000000000..258b3b013c19dfab84dcc239d9b7bf0cf256f402
--- /dev/null
+++ Celo/e2e_test/debug-fee-currency/deploy_and_send_tx.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+#shellcheck disable=SC2034,SC2155,SC2086
+set -xeo pipefail
+
+source ./lib.sh
+
+fee_currency=$(deploy_fee_currency $1 $2 $3)
+cip_64_tx $fee_currency
diff --git op-geth/e2e_test/debug-fee-currency/lib.sh Celo/e2e_test/debug-fee-currency/lib.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a7077c774e4976801a07ce7e0efd4b17ede0dce9
--- /dev/null
+++ Celo/e2e_test/debug-fee-currency/lib.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+#shellcheck disable=SC2034,SC2155,SC2086
+set -xeo pipefail
+
+# args:
+# $1: failOnDebit (bool):
+# if true, this will make the DebugFeeCurrenc.DebitFees() call fail with a revert
+# $2: failOnCredit (bool)
+# if true, this will make the DebugFeeCurrenc.CreditFees() call fail with a revert
+# $3: highGasOnCredit (bool)
+# if true, this will make the DebugFeeCurrenc.CreditFees() call use
+# a high amount of gas
+# returns:
+# deployed fee-currency address
+function deploy_fee_currency() {
+ (
+ local fee_currency=$(
+ forge create --root "$SCRIPT_DIR/debug-fee-currency" --contracts "$SCRIPT_DIR/debug-fee-currency" --private-key $ACC_PRIVKEY DebugFeeCurrency.sol:DebugFeeCurrency --constructor-args '100000000000000000000000000' $1 $2 $3 --json | jq .deployedTo -r
+ )
+ if [ -z "${fee_currency}" ]; then
+ exit 1
+ fi
+ # this always resets the token address for the predeployed oracle3
+ cast send --private-key $ACC_PRIVKEY $ORACLE3 'setExchangeRate(address, uint256, uint256)' $fee_currency 2ether 1ether > /dev/null
+ cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $fee_currency $ORACLE3 60000 > /dev/null
+ echo "$fee_currency"
+ )
+}
+
+# args:
+# $1: feeCurrency (address):
+# deployed fee-currency address to be cleaned up
+function cleanup_fee_currency() {
+ (
+ local fee_currency=$1
+ # HACK: this uses a static index 2, which relies on the fact that all non-predeployed currencies will be always cleaned up
+ # from the directory and the list never has more than 3 elements. Once there is the need for more dynamic removal we
+ # can parse the following call and find the index ourselves:
+ # local currencies=$(cast call "$FEE_CURRENCY_DIRECTORY_ADDR" 'getCurrencies() (address[] memory)')
+ cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'removeCurrencies(address, uint256)' $fee_currency 2 --json | jq 'if .status == "0x1" then 0 else 1 end' -r
+ )
+}
+
+# args:
+# $1: feeCurrencyAddress (string):
+# which fee-currency address to use for the default CIP-64 transaction
+# $2: waitBlocks (num):
+# how many blocks to wait until the lack of a receipt is considered a failure
+# $3: replaceTransaction (bool):
+# replace the transaction with a transaction of higher priority-fee when
+# there is no receipt after the `waitBlocks` time passed
+# $4: value (num):
+# value to send in the transaction
+function cip_64_tx() {
+ $SCRIPT_DIR/js-tests/send_tx.mjs "$(cast chain-id)" $ACC_PRIVKEY $1 $2 $3 $4
+}
+
+# use this function to assert the cip_64_tx return value, by using a pipe like
+# `cip_64_tx "$fee-currency" | assert_cip_64_tx true`
+#
+# args:
+# $1: success (string):
+# expected success value, "true" for when the cip-64 tx should have succeeded, "false" if not
+# $2: error-regex (string):
+# expected RPC return-error value regex to grep for, use "null", "" or unset value if no error is assumed.
+function assert_cip_64_tx() {
+ local value
+ read -r value
+ local expected_error="$2"
+
+ if [ "$(echo "$value" | jq .success)" != "$1" ]; then
+ exit 1
+ fi
+ if [ -z "$expected_error" ]; then
+ expected_error="null"
+ fi
+ echo "$value" | jq .error | grep -qE "$expected_error"
+}
diff --git op-geth/e2e_test/js-tests/Makefile Celo/e2e_test/js-tests/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..69555e594b4419c7c012ddc56d5403d88f4cd142
--- /dev/null
+++ Celo/e2e_test/js-tests/Makefile
@@ -0,0 +1,2 @@
+format:
+ npx @biomejs/biome format --write .
diff --git op-geth/e2e_test/js-tests/package-lock.json Celo/e2e_test/js-tests/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..6c7d7ab2e55228f86c952d6804c40abe28fdb456
--- /dev/null
+++ Celo/e2e_test/js-tests/package-lock.json
@@ -0,0 +1,1232 @@
+{
+ "name": "js-tests",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "js-tests",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "chai": "^5.1.2",
+ "ethers": "^6.10.0",
+ "mocha": "^10.2.0",
+ "viem": "^2.21.18"
+ }
+ },
+ "node_modules/@adraffy/ens-normalize": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz",
+ "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q=="
+ },
+ "node_modules/@noble/curves": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
+ "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
+ "dependencies": {
+ "@noble/hashes": "1.3.2"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
+ "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/base": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
+ "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.5.0.tgz",
+ "integrity": "sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/curves": "~1.6.0",
+ "@noble/hashes": "~1.5.0",
+ "@scure/base": "~1.1.7"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32/node_modules/@noble/curves": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz",
+ "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "1.5.0"
+ },
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32/node_modules/@noble/hashes": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
+ "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip39": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz",
+ "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "~1.5.0",
+ "@scure/base": "~1.1.8"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip39/node_modules/@noble/hashes": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
+ "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "18.15.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz",
+ "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q=="
+ },
+ "node_modules/abitype": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.6.tgz",
+ "integrity": "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/wevm"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.4",
+ "zod": "^3 >=3.22.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/aes-js": {
+ "version": "4.0.0-beta.5",
+ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
+ "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q=="
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chai": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+ "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz",
+ "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ethers": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.10.0.tgz",
+ "integrity": "sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/ethers-io/"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@adraffy/ens-normalize": "1.10.0",
+ "@noble/curves": "1.2.0",
+ "@noble/hashes": "1.3.2",
+ "@types/node": "18.15.13",
+ "aes-js": "4.0.0-beta.5",
+ "tslib": "2.4.0",
+ "ws": "8.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isows": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz",
+ "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "ws": "*"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz",
+ "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==",
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dependencies": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pathval": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+ "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
+ },
+ "node_modules/viem": {
+ "version": "2.21.27",
+ "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.27.tgz",
+ "integrity": "sha512-lBpldSmwmKZ8jIiVLqoplPnuMs2Cza8tK0/G9m+ZjqfbgEp4+BPviFBJWCDNFxSp8H3gPzAuqj8o1WKQyq3wlg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@adraffy/ens-normalize": "1.11.0",
+ "@noble/curves": "1.6.0",
+ "@noble/hashes": "1.5.0",
+ "@scure/bip32": "1.5.0",
+ "@scure/bip39": "1.4.0",
+ "abitype": "1.0.6",
+ "isows": "1.0.6",
+ "webauthn-p256": "0.0.10",
+ "ws": "8.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/viem/node_modules/@adraffy/ens-normalize": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz",
+ "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==",
+ "license": "MIT"
+ },
+ "node_modules/viem/node_modules/@noble/curves": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz",
+ "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "1.5.0"
+ },
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/viem/node_modules/@noble/hashes": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
+ "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/viem/node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webauthn-p256": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.10.tgz",
+ "integrity": "sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@noble/curves": "^1.4.0",
+ "@noble/hashes": "^1.4.0"
+ }
+ },
+ "node_modules/webauthn-p256/node_modules/@noble/curves": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz",
+ "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "1.5.0"
+ },
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/webauthn-p256/node_modules/@noble/hashes": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
+ "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw=="
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/ws": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
+ "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git op-geth/e2e_test/js-tests/package.json Celo/e2e_test/js-tests/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..16167bf83aadfc6ad05bc4568a177e94173522bc
--- /dev/null
+++ Celo/e2e_test/js-tests/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "js-tests",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "chai": "^5.1.2",
+ "ethers": "^6.10.0",
+ "mocha": "^10.2.0",
+ "viem": "^2.21.18"
+ }
+}
diff --git op-geth/e2e_test/js-tests/send_tx.mjs Celo/e2e_test/js-tests/send_tx.mjs
new file mode 100755
index 0000000000000000000000000000000000000000..0e6122810461981b464338c40d82719e99da52cb
--- /dev/null
+++ Celo/e2e_test/js-tests/send_tx.mjs
@@ -0,0 +1,112 @@
+#!/usr/bin/env node
+import {
+ TransactionReceiptNotFoundError,
+} from "viem";
+import { publicClient, walletClient, account } from "./viem_setup.mjs"
+
+const [chainId, privateKey, feeCurrency, waitBlocks, replaceTxAfterWait, celoValue] = process.argv.slice(2);
+
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+async function waitUntilBlock(blocknum) {
+ var next = await publicClient.getBlockNumber({ cacheTime: 0 });
+ while (next < blocknum) {
+ await sleep(500);
+ next = await publicClient.getBlockNumber({ cacheTime: 0 });
+ }
+}
+
+async function getTransactionReceipt(hash) {
+ try {
+ return await publicClient.getTransactionReceipt({ hash: hash });
+ } catch (e) {
+ if (e instanceof TransactionReceiptNotFoundError) {
+ return undefined;
+ }
+ throw e;
+ }
+}
+
+async function replaceTransaction(tx) {
+ const request = await walletClient.prepareTransactionRequest({
+ account: tx.account,
+ to: account.address,
+ value: 0n,
+ gas: 21000,
+ nonce: tx.nonce,
+ maxFeePerGas: tx.maxFeePerGas,
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas + 1000n,
+ });
+ const hash = await walletClient.sendRawTransaction({
+ serializedTransaction: await walletClient.signTransaction(request),
+ });
+ const receipt = await publicClient.waitForTransactionReceipt({
+ hash: hash,
+ confirmations: 1,
+ });
+ return receipt;
+}
+
+async function main() {
+ let value = 2n
+ if (celoValue !== "") {
+ value = BigInt(celoValue)
+ }
+ const request = await walletClient.prepareTransactionRequest({
+ account,
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: value,
+ gas: 90000,
+ feeCurrency,
+ maxFeePerGas: 25000000000n,
+ maxPriorityFeePerGas: 100n, // should be >= 1wei even after conversion to native tokens
+ });
+
+ var hash;
+
+ var blocknum = await publicClient.getBlockNumber({ cacheTime: 0 });
+ var replaced = false;
+ try {
+ hash = await walletClient.sendRawTransaction({
+ serializedTransaction: await walletClient.signTransaction(request),
+ });
+ } catch (e) {
+ // direct revert
+ console.log(
+ JSON.stringify({
+ success: false,
+ replaced: replaced,
+ error: e,
+ }),
+ );
+ return;
+ }
+
+ var success = true;
+ var waitBlocksForReceipt = parseInt(waitBlocks);
+ var receipt = await getTransactionReceipt(hash);
+ while (waitBlocksForReceipt > 0) {
+ await waitUntilBlock(blocknum + BigInt(1));
+ waitBlocksForReceipt--;
+ var receipt = await getTransactionReceipt(hash);
+ }
+ if (!receipt) {
+ if (replaceTxAfterWait == "true") {
+ receipt = await replaceTransaction(request);
+ }
+ success = false;
+ }
+ // print for bash script wrapper return value
+ console.log(
+ JSON.stringify({
+ success: success,
+ replaced: replaced,
+ error: null,
+ }),
+ );
+
+ return receipt;
+}
+await main();
+process.exit(0);
diff --git op-geth/e2e_test/js-tests/test_ethers_tx.mjs Celo/e2e_test/js-tests/test_ethers_tx.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..979150af9fcb52ecfe0b61f7225267adda3bf8e4
--- /dev/null
+++ Celo/e2e_test/js-tests/test_ethers_tx.mjs
@@ -0,0 +1,52 @@
+import { assert } from "chai";
+import "mocha";
+import { ethers } from "ethers";
+
+const provider = new ethers.JsonRpcProvider(process.env.ETH_RPC_URL);
+const signer = new ethers.Wallet(process.env.ACC_PRIVKEY, provider);
+
+describe("ethers.js send tx", () => {
+ it("send basic tx and check receipt", async () => {
+ const tx = await signer.sendTransaction({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 1,
+ });
+ const receipt = await tx.wait();
+ }).timeout(20_000);
+});
+
+describe("ethers.js compatibility tests with state", () => {
+ it("provider.getBlock works (block has gasLimit set)", async () => {
+ let block = await provider.getBlock();
+
+ // These assertions trigger on undefined or null
+ assert.notEqual(block, null);
+ assert.notEqual(block.gasLimit, null);
+ });
+
+ it("EIP-1559 transactions supported (can get feeData)", async () => {
+ const feeData = await provider.getFeeData();
+
+ // These assertions trigger on undefined or null
+ assert.notEqual(feeData, null);
+ assert.notEqual(feeData.maxFeePerGas, null);
+ assert.notEqual(feeData.maxPriorityFeePerGas, null);
+ assert.notEqual(feeData.gasPrice, null);
+ }).timeout(5_000);;
+
+ it("block has gasLimit", async () => {
+ const fullBlock = await provider.send("eth_getBlockByNumber", [
+ "latest",
+ true,
+ ]);
+ assert.isTrue(fullBlock.hasOwnProperty("gasLimit"));
+ });
+
+ it("block has baseFeePerGas", async () => {
+ const fullBlock = await provider.send("eth_getBlockByNumber", [
+ "latest",
+ true,
+ ]);
+ assert.isTrue(fullBlock.hasOwnProperty("baseFeePerGas"));
+ });
+});
diff --git op-geth/e2e_test/js-tests/test_viem_smoketest.mjs Celo/e2e_test/js-tests/test_viem_smoketest.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..ce7cdaa1c591a6e1f41f22e800adf01a2dc0a5f8
--- /dev/null
+++ Celo/e2e_test/js-tests/test_viem_smoketest.mjs
@@ -0,0 +1,86 @@
+import { assert } from "chai";
+import "mocha";
+import {
+ parseAbi,
+} from "viem";
+import fs from "fs";
+import { publicClient, walletClient } from "./viem_setup.mjs"
+
+// Load compiled contract
+const testContractJSON = JSON.parse(fs.readFileSync(process.env.COMPILED_TEST_CONTRACT, 'utf8'));
+
+// check checks that the receipt has status success and that the transaction
+// type matches the expected type, since viem sometimes mangles the type when
+// building txs.
+async function check(txHash, tx_checks, receipt_checks) {
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
+ assert.equal(receipt.status, "success", "receipt status 'failure'");
+ const transaction = await publicClient.getTransaction({ hash: txHash });
+ for (const [key, expected] of Object.entries(tx_checks ?? {})) {
+ assert.equal(transaction[key], expected, `transaction ${key} does not match`);
+ }
+ for (const [key, expected] of Object.entries(receipt_checks ?? {})) {
+ assert.equal(receipt[key], expected, `receipt ${key} does not match`);
+ }
+}
+
+// sendTypedTransaction sends a transaction with the given type and an optional
+// feeCurrency.
+async function sendTypedTransaction(type, feeCurrency) {
+ return await walletClient.sendTransaction({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 1,
+ type: type,
+ feeCurrency: feeCurrency,
+ });
+}
+
+// sendTypedSmartContractTransaction initiates a token transfer with the given type
+// and an optional feeCurrency.
+async function sendTypedSmartContractTransaction(type, feeCurrency) {
+ const abi = parseAbi(['function transfer(address to, uint256 value) external returns (bool)']);
+ return await walletClient.writeContract({
+ abi: abi,
+ address: process.env.TOKEN_ADDR,
+ functionName: 'transfer',
+ args: ['0x00000000000000000000000000000000DeaDBeef', 1n],
+ type: type,
+ feeCurrency: feeCurrency,
+ });
+}
+
+// sendTypedCreateTransaction sends a create transaction with the given type
+// and an optional feeCurrency.
+async function sendTypedCreateTransaction(type, feeCurrency) {
+ return await walletClient.deployContract({
+ type: type,
+ feeCurrency: feeCurrency,
+ bytecode: testContractJSON.bytecode.object,
+ abi: testContractJSON.abi,
+ // The constructor args for the test contract at ../debug-fee-currency/DebugFeeCurrency.sol
+ args: [1n, true, true, true],
+ });
+}
+
+["legacy", "eip2930", "eip1559", "cip64"].forEach(function (type) {
+ describe("viem smoke test, tx type " + type, () => {
+ const feeCurrency = type == "cip64" ? process.env.FEE_CURRENCY.toLowerCase() : undefined;
+ let l1Fee = 0n;
+ if (!process.env.NETWORK) {
+ // Local dev chain does not have L1 fees (Optimism is unset)
+ l1Fee = undefined;
+ }
+ it("send tx", async () => {
+ const send = await sendTypedTransaction(type, feeCurrency);
+ await check(send, {type, feeCurrency}, {l1Fee});
+ });
+ it("send create tx", async () => {
+ const create = await sendTypedCreateTransaction(type, feeCurrency);
+ await check(create, {type, feeCurrency}, {l1Fee});
+ });
+ it("send contract interaction tx", async () => {
+ const contract = await sendTypedSmartContractTransaction(type, feeCurrency);
+ await check(contract, {type, feeCurrency}, {l1Fee});
+ });
+ });
+});
diff --git op-geth/e2e_test/js-tests/test_viem_tx.mjs Celo/e2e_test/js-tests/test_viem_tx.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..e42c7ba7934bfc57506cfd44a7b0c6216b7116d3
--- /dev/null
+++ Celo/e2e_test/js-tests/test_viem_tx.mjs
@@ -0,0 +1,328 @@
+import { assert } from "chai";
+import "mocha";
+import {
+ parseAbi,
+} from "viem";
+import { publicClient, walletClient } from "./viem_setup.mjs"
+
+// Returns the base fee per gas for the current block multiplied by 2 to account for any increase in the subsequent block.
+async function getGasFees(publicClient, tip, feeCurrency) {
+ const rate = await getRate(feeCurrency);
+ const b = await publicClient.getBlock();
+ const tipInFeeCurrency = rate.toFeeCurrency(tip);
+ return [rate.toFeeCurrency(b.baseFeePerGas) + tipInFeeCurrency, tipInFeeCurrency];
+}
+
+const testNonceBump = async (
+ firstCap,
+ firstCurrency,
+ secondCap,
+ secondCurrency,
+ shouldReplace,
+) => {
+ const syncBarrierRequest = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ gas: 22000,
+ });
+ const firstTxHash = await walletClient.sendTransaction({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ gas: 171000,
+ maxFeePerGas: firstCap,
+ maxPriorityFeePerGas: firstCap,
+ nonce: syncBarrierRequest.nonce + 1,
+ feeCurrency: firstCurrency,
+ });
+ var secondTxHash;
+ try {
+ secondTxHash = await walletClient.sendTransaction({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 3,
+ gas: 171000,
+ maxFeePerGas: secondCap,
+ maxPriorityFeePerGas: secondCap,
+ nonce: syncBarrierRequest.nonce + 1,
+ feeCurrency: secondCurrency,
+ });
+ } catch (err) {
+ // If shouldReplace, no error should be thrown
+ // If shouldReplace == false, exactly the underpriced error should be thrown
+ if (
+ err.cause.details != "replacement transaction underpriced" ||
+ shouldReplace
+ ) {
+ throw err; // Only throw if unexpected error.
+ }
+ }
+ const syncBarrierSignature =
+ await walletClient.signTransaction(syncBarrierRequest);
+ const barrierTxHash = await walletClient.sendRawTransaction({
+ serializedTransaction: syncBarrierSignature,
+ });
+ await publicClient.waitForTransactionReceipt({ hash: barrierTxHash });
+ if (shouldReplace) {
+ // The new transaction was included.
+ await publicClient.waitForTransactionReceipt({ hash: secondTxHash });
+ } else {
+ // The original transaction was not replaced.
+ await publicClient.waitForTransactionReceipt({ hash: firstTxHash });
+ }
+};
+
+describe("viem send tx", () => {
+ it("send basic tx and check receipt", async () => {
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 1,
+ gas: 21000,
+ });
+ const signature = await walletClient.signTransaction(request);
+ const hash = await walletClient.sendRawTransaction({
+ serializedTransaction: signature,
+ });
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
+ assert.equal(receipt.status, "success", "receipt status 'failure'");
+ }).timeout(10_000);
+
+ it("send basic tx using viem gas estimation and check receipt", async () => {
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 1,
+ });
+ const signature = await walletClient.signTransaction(request);
+ const hash = await walletClient.sendRawTransaction({
+ serializedTransaction: signature,
+ });
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
+ assert.equal(receipt.status, "success", "receipt status 'failure'");
+ }).timeout(10_000);
+
+ it("send fee currency tx with explicit gas fields and check receipt", async () => {
+ const [maxFeePerGas, tip] = await getGasFees(publicClient, 2n, process.env.FEE_CURRENCY);
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ gas: 171000,
+ feeCurrency: process.env.FEE_CURRENCY,
+ maxFeePerGas: maxFeePerGas,
+ maxPriorityFeePerGas: tip,
+ });
+ const signature = await walletClient.signTransaction(request);
+ const hash = await walletClient.sendRawTransaction({
+ serializedTransaction: signature,
+ });
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
+ assert.equal(receipt.status, "success", "receipt status 'failure'");
+ }).timeout(10_000);
+
+ it("test gas price difference for fee currency", async () => {
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ gas: 171000,
+ feeCurrency: process.env.FEE_CURRENCY,
+ });
+
+ // Get the raw gas price and maxPriorityFeePerGas
+ const gasPriceNative = await publicClient.getGasPrice({});
+ var maxPriorityFeePerGasNative =
+ await publicClient.estimateMaxPriorityFeePerGas({});
+ const block = await publicClient.getBlock({});
+
+ // Check them against the base fee.
+ assert.equal(
+ BigInt(block.baseFeePerGas) + maxPriorityFeePerGasNative,
+ gasPriceNative,
+ );
+
+ // viem's getGasPrice does not expose additional request parameters, but
+ // Celo's override 'chain.fees.estimateFeesPerGas' action does. This will
+ // call the eth_gasPrice and eth_maxPriorityFeePerGas methods with the
+ // additional feeCurrency parameter internally, it also multiplies the base
+ // fee component of the maxFeePerGas by a multiplier which by default is
+ // 1.2 or (12n/10n).
+ var fees = await publicClient.estimateFeesPerGas({
+ type: "eip1559",
+ request: {
+ feeCurrency: process.env.FEE_CURRENCY,
+ },
+ });
+
+ // Get the exchange rates for the fee currency.
+ const abi = parseAbi(['function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator)']);
+ const [numerator, denominator] = await publicClient.readContract({
+ address: process.env.FEE_CURRENCY_DIRECTORY_ADDR,
+ abi: abi,
+ functionName: 'getExchangeRate',
+ args: [process.env.FEE_CURRENCY],
+ });
+
+ // TODO fix this when viem is fixed - https://github.com/celo-org/viem/pull/20
+ // The expected value for the max fee should be the (baseFeePerGas * multiplier) + maxPriorityFeePerGas
+ // Instead what is currently returned is (maxFeePerGas * multiplier) + maxPriorityFeePerGas
+ const maxPriorityFeeInFeeCurrency = (maxPriorityFeePerGasNative * numerator) / denominator;
+ const maxFeeInFeeCurrency = ((block.baseFeePerGas + maxPriorityFeePerGasNative) * numerator) / denominator;
+ assert.equal(fees.maxFeePerGas, ((maxFeeInFeeCurrency * 12n) / 10n) + maxPriorityFeeInFeeCurrency);
+ assert.equal(fees.maxPriorityFeePerGas, maxPriorityFeeInFeeCurrency);
+
+ // check that the prepared transaction request uses the
+ // converted gas price internally
+ assert.equal(request.maxFeePerGas, fees.maxFeePerGas);
+ assert.equal(request.maxPriorityFeePerGas, fees.maxPriorityFeePerGas);
+ }).timeout(10_000);
+
+ it("send fee currency with gas estimation tx and check receipt", async () => {
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ feeCurrency: process.env.FEE_CURRENCY,
+ maxFeePerGas: 50000000000n,
+ maxPriorityFeePerGas: 2n,
+ });
+ const signature = await walletClient.signTransaction(request);
+ const hash = await walletClient.sendRawTransaction({
+ serializedTransaction: signature,
+ });
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
+ assert.equal(receipt.status, "success", "receipt status 'failure'");
+ }).timeout(10_000);
+
+ // The goal is this test is to ensure that fee currencies are correctly
+ // taken into account when performing tx replacements. As such we want the
+ // prices that we use for the failed and successful tx replacements to be
+ // close to the threshold value, such that an invalid currency conversion is
+ // more liable to result in a failure.
+ it("send overlapping nonce tx in different currencies", async () => {
+ // Note the threshold for a price bump to be accepted is 10%, i.e >= oldPrice * 1.1
+ const priceBump = 1.1; // minimum bump percentage to replace a transaction
+ const priceNearBump = 1.09; // slightly lower percentage than the price bump
+
+ const rate = await getRate(process.env.FEE_CURRENCY);
+ // Native to FEE_CURRENCY
+ const nativeCap = 30_000_000_000;
+ const bumpCurrencyCap = rate.toFeeCurrency(BigInt(Math.round(nativeCap * priceBump)));
+ const failToBumpCurrencyCap = rate.toFeeCurrency(BigInt(
+ Math.round(nativeCap * priceNearBump)
+ ));
+ const tokenCurrency = process.env.FEE_CURRENCY;
+ const nativeCurrency = null;
+ await testNonceBump(
+ nativeCap,
+ nativeCurrency,
+ bumpCurrencyCap,
+ tokenCurrency,
+ true,
+ );
+ await testNonceBump(
+ nativeCap,
+ nativeCurrency,
+ failToBumpCurrencyCap,
+ tokenCurrency,
+ false,
+ );
+
+ // FEE_CURRENCY to Native
+ const currencyCap = 60_000_000_000;
+ const bumpNativeCap = rate.toNative(BigInt(Math.round(currencyCap * priceBump)));
+ const failToBumpNativeCap = rate.toNative(BigInt(
+ Math.round(currencyCap * priceNearBump)
+ ));
+ await testNonceBump(
+ currencyCap,
+ tokenCurrency,
+ bumpNativeCap,
+ nativeCurrency,
+ true,
+ );
+ await testNonceBump(
+ currencyCap,
+ tokenCurrency,
+ failToBumpNativeCap,
+ nativeCurrency,
+ false,
+ );
+ }).timeout(60_000);
+
+ it("send tx with unregistered fee currency", async () => {
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ gas: 171000,
+ feeCurrency: "0x000000000000000000000000000000000badc310",
+ maxFeePerGas: 1000000000n,
+ maxPriorityFeePerGas: 1n,
+ });
+ const signature = await walletClient.signTransaction(request);
+ try {
+ await walletClient.sendRawTransaction({
+ serializedTransaction: signature,
+ });
+ assert.fail("Failed to filter unregistered feeCurrency");
+ } catch (err) {
+ // TODO: find a better way to check the error type
+ if (err.cause.details.indexOf("unregistered fee-currency address") >= 0) {
+ // Test success
+ } else {
+ throw err;
+ }
+ }
+ }).timeout(10_000);
+
+ it("send fee currency tx with just high enough gas price", async () => {
+ // The idea of this test is to check that the fee currency is taken into
+ // account by the server. We do this by using a fee currency that has a
+ // value greater than celo, so that the base fee in fee currency becomes a
+ // number significantly lower than the base fee in celo. If the server
+ // doesn't take into account the fee currency then it will reject the
+ // transaction because the maxFeePerGas will be too low.
+
+ // If we are running local tests we use FEE_CURRENCY2 since it is worth
+ // double the value of celo, otherwise we use FEE_CURRENCY which is USDC
+ // end currently worth roughly double the value of celo.
+ const fc = process.env.NETWORK == null ? process.env.FEE_CURRENCY2 : process.env.FEE_CURRENCY;
+ const rate = await getRate(fc);
+ const block = await publicClient.getBlock({});
+ // We increment the base fee by 10% to cover the case where the base fee increases next block.
+ const convertedBaseFee = rate.toFeeCurrency(block.baseFeePerGas * 11n/10n);
+
+ // Check that the converted base fee value is still below the native base
+ // fee value, if this check fails we will need to consider an alternative
+ // fee currency to USDC for network tests.
+ if (convertedBaseFee >= block.baseFeePerGas) {
+ assert.fail(`Converted base fee (${convertedBaseFee}) not less than native base fee (${block.baseFeePerGas})`);
+ }
+ const maxFeePerGas = convertedBaseFee + 2n;
+ const request = await walletClient.prepareTransactionRequest({
+ to: "0x00000000000000000000000000000000DeaDBeef",
+ value: 2,
+ gas: 171000,
+ feeCurrency: process.env.FEE_CURRENCY,
+ feeCurrency: fc,
+ maxFeePerGas: maxFeePerGas,
+ maxPriorityFeePerGas: 2n,
+ });
+ const signature = await walletClient.signTransaction(request);
+ const hash = await walletClient.sendRawTransaction({
+ serializedTransaction: signature,
+ });
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
+ assert.equal(receipt.status, "success", "receipt status 'failure'");
+ assert.isAtMost(Number(receipt.effectiveGasPrice), Number(maxFeePerGas), "effective gas price is too high");
+ assert.isAbove(Number(receipt.effectiveGasPrice), Number(maxFeePerGas) * 0.7, "effective gas price is too low");
+ }).timeout(10_000);
+});
+
+async function getRate(feeCurrencyAddress) {
+ const abi = parseAbi(['function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator)']);
+ const [numerator, denominator] = await publicClient.readContract({
+ address: process.env.FEE_CURRENCY_DIRECTORY_ADDR,
+ abi: abi,
+ functionName: 'getExchangeRate',
+ args: [feeCurrencyAddress],
+ });
+ return {
+ toFeeCurrency: (v) => (v * numerator) / denominator,
+ toNative: (v) => (v * denominator) / numerator,
+ };
+}
diff --git op-geth/e2e_test/js-tests/viem_setup.mjs Celo/e2e_test/js-tests/viem_setup.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..664f1f75f2ce6a580dc6b0e935c8e746b4b91c23
--- /dev/null
+++ Celo/e2e_test/js-tests/viem_setup.mjs
@@ -0,0 +1,44 @@
+import { assert } from "chai";
+import "mocha";
+import {
+ createPublicClient,
+ createWalletClient,
+ http,
+ defineChain,
+} from "viem";
+import { celoAlfajores } from "viem/chains";
+import { privateKeyToAccount } from "viem/accounts";
+
+// Setup up chain
+const devChain = defineChain({
+ ...celoAlfajores,
+ id: 1337,
+ name: "local dev chain",
+ network: "dev",
+ rpcUrls: {
+ default: {
+ http: [process.env.ETH_RPC_URL],
+ },
+ },
+});
+
+const chain = (() => {
+ switch (process.env.NETWORK) {
+ case 'alfajores':
+ return celoAlfajores
+ default:
+ return devChain
+ };
+})();
+
+// Set up clients/wallet
+export const publicClient = createPublicClient({
+ chain: chain,
+ transport: http(),
+});
+export const account = privateKeyToAccount(process.env.ACC_PRIVKEY);
+export const walletClient = createWalletClient({
+ account,
+ chain: chain,
+ transport: http(),
+});
\ No newline at end of file
diff --git op-geth/e2e_test/run_all_tests.sh Celo/e2e_test/run_all_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9d0675abd15b2c291a8f648712fdc597daad0d96
--- /dev/null
+++ Celo/e2e_test/run_all_tests.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+set -eo pipefail
+
+SCRIPT_DIR=$(readlink -f "$(dirname "$0")")
+source "$SCRIPT_DIR/shared.sh"
+
+TEST_GLOB=$1
+
+if [ -z $NETWORK ]; then
+ ## Start geth
+ cd "$SCRIPT_DIR/.." || exit 1
+ make geth
+ trap 'kill %%' EXIT # kill bg job at exit
+ build/bin/geth --dev --http --http.api eth,web3,net --txpool.nolocals &>"$SCRIPT_DIR/geth.log" &
+
+ # Wait for geth to be ready
+ for _ in {1..10}; do
+ if cast block &>/dev/null; then
+ break
+ fi
+ sleep 0.2
+ done
+
+ ## Run tests
+ echo Geth ready, start tests
+fi
+
+cd "$SCRIPT_DIR" || exit 1
+# There's a problem with geth return errors on the first transaction sent.
+# See https://github.com/ethereum/web3.py/issues/3212
+# To work around this, send a transaction before running tests
+cast send --json --private-key "$ACC_PRIVKEY" "$TOKEN_ADDR" 'transfer(address to, uint256 value) returns (bool)' 0x000000000000000000000000000000000000dEaD 100 > /dev/null || true
+
+failures=0
+tests=0
+echo "Globbing with \"$TEST_GLOB\""
+for f in test_*"$TEST_GLOB"*; do
+ echo "for file $f"
+ if [[ -n $NETWORK ]]; then
+ case $f in
+ # Skip tests that require a local network.
+ test_fee_currency_fails_on_credit.sh|test_fee_currency_fails_on_debit.sh|test_fee_currency_fails_intrinsic.sh|test_value_and_fee_currency_balance_check.sh)
+ echo "skipping file $f"
+ continue
+ ;;
+ esac
+ fi
+ echo -e "\nRun $f"
+ if "./$f"; then
+ tput setaf 2 || true
+ echo "PASS $f"
+ else
+ tput setaf 1 || true
+ echo "FAIL $f ❌"
+ ((failures++)) || true
+ fi
+ tput sgr0 || true
+ ((tests++)) || true
+done
+
+## Final summary
+echo
+if [[ $failures -eq 0 ]]; then
+ tput setaf 2 || true
+ echo All $tests tests succeeded!
+else
+ tput setaf 1 || true
+ echo $failures/$tests failed.
+fi
+tput sgr0 || true
+exit $failures
diff --git op-geth/e2e_test/shared.sh Celo/e2e_test/shared.sh
new file mode 100644
index 0000000000000000000000000000000000000000..2a4207bb9ae5e9c2dae5c485a8903393927ded98
--- /dev/null
+++ Celo/e2e_test/shared.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+#shellcheck disable=SC2034 # unused vars make sense in a shared file
+
+SCRIPT_DIR=$(readlink -f "$(dirname "$0")")
+export SCRIPT_DIR
+
+case $NETWORK in
+ alfajores)
+ export ETH_RPC_URL=https://alfajores-forno.celo-testnet.org
+ export TOKEN_ADDR=0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9
+ export FEE_HANDLER=0xEAaFf71AB67B5d0eF34ba62Ea06Ac3d3E2dAAA38
+ export FEE_CURRENCY=0x4822e58de6f5e485eF90df51C41CE01721331dC0
+ echo "Using Alfajores network"
+ ;;
+ '')
+ export ETH_RPC_URL=http://127.0.0.1:8545
+ export TOKEN_ADDR=0x471ece3750da237f93b8e339c536989b8978a438
+ export FEE_HANDLER=0xcd437749e43a154c07f3553504c68fbfd56b8778
+ export FEE_CURRENCY=0x000000000000000000000000000000000000ce16
+ export FEE_CURRENCY2=0x000000000000000000000000000000000000ce17
+ echo "Using local network"
+ ;;
+esac
+
+export ACC_ADDR=0x42cf1bbc38BaAA3c4898ce8790e21eD2738c6A4a
+export ACC_PRIVKEY=0x2771aff413cac48d9f8c114fabddd9195a2129f3c2c436caa07e27bb7f58ead5
+export REGISTRY_ADDR=0x000000000000000000000000000000000000ce10
+export FEE_CURRENCY_DIRECTORY_ADDR=0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF
+export ORACLE3=0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0003
+
+export FIXIDITY_1=1000000000000000000000000
+export ZERO_ADDRESS=0x0000000000000000000000000000000000000000
+
+prepare_node() {
+ (
+ cd js-tests || exit 1
+ [[ -d node_modules ]] || npm install
+ )
+}
diff --git op-geth/e2e_test/smoketest_unsupported_txs/unsupported_txs_test.go Celo/e2e_test/smoketest_unsupported_txs/unsupported_txs_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..781e520b153e1e905387fb3b983764f18b340157
--- /dev/null
+++ Celo/e2e_test/smoketest_unsupported_txs/unsupported_txs_test.go
@@ -0,0 +1,105 @@
+//go:build smoketest
+
+package smoketestunsupportedtxs
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "flag"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ privateKey string
+ opGethRpcURL string
+ feeCurrency string
+ deadAddr = common.HexToAddress("0x00000000000000000000000000000000DeaDBeef")
+)
+
+func init() {
+ // Define your custom flag
+ flag.StringVar(&privateKey, "private-key", "", "private key of transaction sender")
+ flag.StringVar(&opGethRpcURL, "op-geth-url", "", "op-geth rpc url")
+ flag.StringVar(&feeCurrency, "fee-currency", "", "address of the fee currency to use")
+}
+
+func TestTxSendingFails(t *testing.T) {
+ key, err := parsePrivateKey(privateKey)
+ require.NoError(t, err)
+
+ feeCurrencyAddr := common.HexToAddress(feeCurrency)
+
+ client, err := ethclient.Dial(opGethRpcURL)
+ require.NoError(t, err)
+
+ chainId, err := client.ChainID(context.Background())
+ require.NoError(t, err)
+
+ // Get a signer that can sign deprecated txs, we need cel2 configured but not active yet.
+ cel2Time := uint64(1)
+ signer := types.MakeSigner(¶ms.ChainConfig{Cel2Time: &cel2Time, ChainID: chainId}, big.NewInt(0), 0)
+
+ t.Run("CeloLegacy", func(t *testing.T) {
+ gasPrice, err := client.SuggestGasPriceForCurrency(context.Background(), &feeCurrencyAddr)
+ require.NoError(t, err)
+
+ nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(key.PublicKey))
+ require.NoError(t, err)
+
+ txdata := &types.LegacyTx{
+ Nonce: nonce,
+ To: &deadAddr,
+ Gas: 100_000,
+ GasPrice: gasPrice,
+ FeeCurrency: &feeCurrencyAddr,
+ Value: big.NewInt(1),
+ CeloLegacy: true,
+ }
+
+ tx, err := types.SignNewTx(key, signer, txdata)
+ require.NoError(t, err)
+
+ // we expect this to fail because the tx is not supported.
+ err = client.SendTransaction(context.Background(), tx)
+ require.Error(t, err)
+ })
+
+ t.Run("CIP42", func(t *testing.T) {
+ gasFeeCap, err := client.SuggestGasPriceForCurrency(context.Background(), &feeCurrencyAddr)
+ require.NoError(t, err)
+
+ nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(key.PublicKey))
+ require.NoError(t, err)
+
+ txdata := &types.CeloDynamicFeeTx{
+ Nonce: nonce,
+ To: &deadAddr,
+ Gas: 100_000,
+ GasFeeCap: gasFeeCap,
+ FeeCurrency: &feeCurrencyAddr,
+ Value: big.NewInt(1),
+ }
+
+ tx, err := types.SignNewTx(key, signer, txdata)
+ require.NoError(t, err)
+
+ // we expect this to fail because the tx is not supported.
+ err = client.SendTransaction(context.Background(), tx)
+ require.Error(t, err)
+ })
+}
+
+func parsePrivateKey(privateKey string) (*ecdsa.PrivateKey, error) {
+ if len(privateKey) >= 2 && privateKey[0] == '0' && (privateKey[1] == 'x' || privateKey[1] == 'X') {
+ privateKey = privateKey[2:]
+ }
+ return crypto.HexToECDSA(privateKey)
+}
diff --git op-geth/e2e_test/test_base_fee_recipient.sh Celo/e2e_test/test_base_fee_recipient.sh
new file mode 100755
index 0000000000000000000000000000000000000000..030c130bf25e769f4ff6eca8d8806ad012c8de76
--- /dev/null
+++ Celo/e2e_test/test_base_fee_recipient.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+#shellcheck disable=SC2086
+set -eo pipefail
+set -x
+
+source shared.sh
+
+# Send token and check balance
+tx_json=$(cast send --json --private-key $ACC_PRIVKEY $TOKEN_ADDR 'transfer(address to, uint256 value) returns (bool)' 0x000000000000000000000000000000000000dEaD 100)
+block_number=$(echo $tx_json | jq -r '.blockNumber' | cast to-dec)
+block=$(cast block --json --full $block_number)
+gas_used=$(echo $block | jq -r '.gasUsed' | cast to-dec)
+base_fee=$(echo $block | jq -r '.baseFeePerGas' | cast to-dec)
+if [[ -n $NETWORK ]]; then
+ # Every block in a non dev system contains a system tx that pays nothing for gas so we must subtract this from the total gas per block
+ system_tx_hash=$(echo $block | jq -r '.transactions[] | select(.from | ascii_downcase == "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001") | .hash')
+ system_tx_gas=$(cast receipt --json $system_tx_hash | jq -r '.cumulativeGasUsed' | cast to-dec)
+ gas_used=$((gas_used - system_tx_gas))
+fi
+balance_before=$(cast balance --block $((block_number-1)) $FEE_HANDLER)
+balance_after=$(cast balance --block $block_number $FEE_HANDLER)
+balance_change=$((balance_after - balance_before))
+expected_balance_change=$((base_fee * gas_used))
+[[ $expected_balance_change -eq $balance_change ]] || (
+ echo "Balance did not change as expected"
+ exit 1
+)
diff --git op-geth/e2e_test/test_ethers_tx.sh Celo/e2e_test/test_ethers_tx.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8c94521b73ab4e314f2b965fa9896b4f39d538cd
--- /dev/null
+++ Celo/e2e_test/test_ethers_tx.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -eo pipefail
+
+source shared.sh
+prepare_node
+
+cd js-tests && ./node_modules/mocha/bin/mocha.js test_ethers_tx.mjs
diff --git op-geth/e2e_test/test_fee_currency_fails_intrinsic.sh Celo/e2e_test/test_fee_currency_fails_intrinsic.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b34a2b0e742e5045f641d7473470ffc328f98174
--- /dev/null
+++ Celo/e2e_test/test_fee_currency_fails_intrinsic.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#shellcheck disable=SC2086
+set -eo pipefail
+
+source shared.sh
+source debug-fee-currency/lib.sh
+
+# Expect that the creditGasFees failed and is logged by geth
+tail -F -n 0 geth.log >debug-fee-currency/geth.intrinsic.log & # start log capture
+trap 'kill %%' EXIT # kill bg tail job on exit
+(
+ sleep 0.2
+ fee_currency=$(deploy_fee_currency false false true)
+
+ # trigger the first failed call to the CreditFees(), causing the
+ # currency to get temporarily blocklisted.
+ # initial tx should not succeed, should have required a replacement transaction.
+ cip_64_tx $fee_currency 1 true 2 | assert_cip_64_tx false
+
+ sleep 2
+
+ # since the fee currency is temporarily blocked,
+ # this should NOT make the transaction execute anymore,
+ # but invalidate the transaction earlier.
+ # initial tx should not succeed, should have required a replacement transaction.
+ cip_64_tx $fee_currency 1 true 2 | assert_cip_64_tx false
+
+ cleanup_fee_currency $fee_currency
+)
+sleep 0.5
+# although we sent a transaction wih faulty fee-currency twice,
+# the EVM call should have been executed only once
+if [ "$(grep -Ec "fee-currency EVM execution error, temporarily blocking fee-currency in local txpools .+ surpassed maximum allowed intrinsic gas for CreditFees\(\) in fee-currency" debug-fee-currency/geth.intrinsic.log)" -ne 1 ]; then exit 1; fi
diff --git op-geth/e2e_test/test_fee_currency_fails_on_credit.sh Celo/e2e_test/test_fee_currency_fails_on_credit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cea2f470a332cdd25a3c4fd998705366e327f651
--- /dev/null
+++ Celo/e2e_test/test_fee_currency_fails_on_credit.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#shellcheck disable=SC2086
+set -eo pipefail
+
+source shared.sh
+source debug-fee-currency/lib.sh
+
+tail -F -n0 geth.log >debug-fee-currency/geth.partial.log & # start log capture
+trap 'kill %%' EXIT # kill bg tail job on exit
+(
+ sleep 0.2
+ fee_currency=$(deploy_fee_currency false true false)
+
+ # trigger the first failed call to the CreditFees(), causing the
+ # currency to get temporarily blocklisted.
+ # initial tx should not succeed, should have required a replacement transaction.
+ cip_64_tx $fee_currency 1 true 2 | assert_cip_64_tx false
+
+ sleep 2
+
+ # since the fee currency is temporarily blocked,
+ # this should NOT make the transaction execute anymore,
+ # but invalidate the transaction earlier.
+ # initial tx should not succeed, should have required a replacement transaction.
+ cip_64_tx $fee_currency 1 true 2 | assert_cip_64_tx false
+
+ cleanup_fee_currency $fee_currency
+)
+sleep 0.5
+# although we sent a transaction wih faulty fee-currency twice,
+# the EVM call should have been executed only once
+grep "" debug-fee-currency/geth.partial.log
+if [ "$(grep -Ec "fee-currency EVM execution error, temporarily blocking fee-currency in local txpools .+ This DebugFeeCurrency always fails in \(old\) creditGasFees!" debug-fee-currency/geth.partial.log)" -ne 1 ]; then exit 1; fi
diff --git op-geth/e2e_test/test_fee_currency_fails_on_debit.sh Celo/e2e_test/test_fee_currency_fails_on_debit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4285e61753f74da1b82826d9583a82de5f926dd2
--- /dev/null
+++ Celo/e2e_test/test_fee_currency_fails_on_debit.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+#shellcheck disable=SC2086
+set -eo pipefail
+
+source shared.sh
+source debug-fee-currency/lib.sh
+
+# Expect that the debitGasFees fails during tx submission
+#
+fee_currency=$(deploy_fee_currency true false false)
+# this fails during the RPC call, since the DebitFees() is part of the pre-validation
+cip_64_tx $fee_currency 1 false 2 | assert_cip_64_tx false "fee-currency internal error"
+
+cleanup_fee_currency $fee_currency
diff --git op-geth/e2e_test/test_smoketest.sh Celo/e2e_test/test_smoketest.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7358d314a404ed0e80b385ac8f5c97ebed8b057b
--- /dev/null
+++ Celo/e2e_test/test_smoketest.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -eo pipefail
+
+source shared.sh
+prepare_node
+
+(cd debug-fee-currency && forge build --out $PWD/out $PWD)
+export COMPILED_TEST_CONTRACT=../debug-fee-currency/out/DebugFeeCurrency.sol/DebugFeeCurrency.json
+(cd js-tests && ./node_modules/mocha/bin/mocha.js test_viem_smoketest.mjs --timeout 25000 --exit)
+echo go test -v ./smoketest_unsupported -op-geth-url $ETH_RPC_URL -private-key $ACC_PRIVKEY -fee-currency $FEE_CURRENCY
+go test -v ./smoketest_unsupported_txs -tags smoketest -op-geth-url $ETH_RPC_URL -private-key $ACC_PRIVKEY -fee-currency $FEE_CURRENCY
diff --git op-geth/e2e_test/test_token_duality.sh Celo/e2e_test/test_token_duality.sh
new file mode 100755
index 0000000000000000000000000000000000000000..355afef8c7ca71f39454d1e452c60d2046b6ebf8
--- /dev/null
+++ Celo/e2e_test/test_token_duality.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#shellcheck disable=SC2086
+set -eo pipefail
+
+source shared.sh
+
+# Send token and check balance
+balance_before=$(cast balance 0x000000000000000000000000000000000000dEaD)
+cast send --private-key $ACC_PRIVKEY $TOKEN_ADDR 'transfer(address to, uint256 value) returns (bool)' 0x000000000000000000000000000000000000dEaD 100
+balance_after=$(cast balance 0x000000000000000000000000000000000000dEaD)
+echo "Balance change: $balance_before -> $balance_after"
+[[ $((balance_before + 100)) -eq $balance_after ]] || (echo "Balance did not change as expected"; exit 1)
diff --git op-geth/e2e_test/test_value_and_fee_currency_balance_check.sh Celo/e2e_test/test_value_and_fee_currency_balance_check.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7384f067b9234d9f5301265bc6a0abe04ef90aa9
--- /dev/null
+++ Celo/e2e_test/test_value_and_fee_currency_balance_check.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+#shellcheck disable=SC2086
+set -eo pipefail
+set -x
+
+source shared.sh
+source debug-fee-currency/lib.sh
+
+TEST_ACCOUNT_ADDR=0xEa787f769d66B5C131319f262F07254790985BdC
+TEST_ACCOUNT_PRIVKEY=0xd36ad839c0bc4bfd8c718a3219591a791871dafad2391149153e6abb43a777fd
+
+fee_currency=$(deploy_fee_currency false false false)
+
+# Send 2.5e15 fee currency to test account
+cast send --private-key $ACC_PRIVKEY $fee_currency 'transfer(address to, uint256 value) returns (bool)' $TEST_ACCOUNT_ADDR 2500000000000000
+# Send 1e18 celo to test account
+cast send --private-key $ACC_PRIVKEY $TOKEN_ADDR 'transfer(address to, uint256 value) returns (bool)' $TEST_ACCOUNT_ADDR 1000000000000000000
+
+# balanceFeeCurrency=2.5e15, txCost=2.25e15, balanceCelo=1e18, valueCelo=1e15
+# this should succed because the value and the txCost should not be added (with the bug the total cost could be 3.25e15 will fail because the balanceFeeCurrency is 2.5e15)
+$SCRIPT_DIR/js-tests/send_tx.mjs "$(cast chain-id)" $TEST_ACCOUNT_PRIVKEY $fee_currency 1 false 1000000000000000 | assert_cip_64_tx true ""
+
+cleanup_fee_currency $fee_currency
\ No newline at end of file
diff --git op-geth/e2e_test/test_viem_tx.sh Celo/e2e_test/test_viem_tx.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0e4d001eda411d32e1bbca49ed3e98fbe72dc23d
--- /dev/null
+++ Celo/e2e_test/test_viem_tx.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -eo pipefail
+
+source shared.sh
+prepare_node
+
+cd js-tests && ./node_modules/mocha/bin/mocha.js test_viem_tx.mjs --exit
Other changes
+7350
-297
diff --git op-geth/.gitignore Celo/.gitignore
index a1d48cc72b4d31752bc89708f24d83c6e50255cd..dc42ea9429c5a2e7e435ed7827c289f5b546be53 100644
--- op-geth/.gitignore
+++ Celo/.gitignore
@@ -14,6 +14,7 @@ .ethtest
*/**/*tx_database*
*/**/*dapps*
build/_vendor/pkg
+node_modules
#*
.#*
@@ -58,3 +59,4 @@ **/yarn-error.log
logs/
tests/spec-tests/
+e2e_test/debug-fee-currency/*.log
diff --git op-geth/Dockerfile.bootnode Celo/Dockerfile.bootnode
new file mode 100644
index 0000000000000000000000000000000000000000..53f5cbeb56e9732dc410bc3d6757fee24530f607
--- /dev/null
+++ Celo/Dockerfile.bootnode
@@ -0,0 +1,33 @@
+# Support setting various labels on the final image
+ARG COMMIT=""
+ARG VERSION=""
+ARG BUILDNUM=""
+
+# Build Geth bootnode in a stock Go builder container
+FROM golang:1.22-alpine as builder
+
+RUN apk add --no-cache gcc musl-dev linux-headers git
+
+# Get dependencies - will also be cached if we won't change go.mod/go.sum
+COPY go.mod /go-ethereum/
+COPY go.sum /go-ethereum/
+RUN cd /go-ethereum && go mod download
+
+ADD . /go-ethereum
+RUN cd /go-ethereum && go run build/ci.go install -static ./cmd/bootnode
+
+# Pull Geth bootnode into a second stage deploy alpine container
+FROM alpine:latest
+
+RUN apk add --no-cache ca-certificates
+COPY --from=builder /go-ethereum/build/bin/bootnode /usr/local/bin/
+
+EXPOSE 8545 8546 30303 30303/udp
+ENTRYPOINT ["bootnode"]
+
+# Add some metadata labels to help programmatic image consumption
+ARG COMMIT=""
+ARG VERSION=""
+ARG BUILDNUM=""
+
+LABEL commit="$COMMIT" version="$VERSION" buildnum="$BUILDNUM"
diff --git op-geth/accounts/external/backend.go Celo/accounts/external/backend.go
index 62322753daa8df4fecb640aba637ac97e202327d..3289bb0a6e700363c2114f7c3cb472b849af701e 100644
--- op-geth/accounts/external/backend.go
+++ Celo/accounts/external/backend.go
@@ -215,7 +215,7 @@ }
switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
- case types.DynamicFeeTxType, types.BlobTxType:
+ case types.DynamicFeeTxType, types.BlobTxType, types.CeloDynamicFeeTxV2Type, types.CeloDenominatedTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default:
diff --git op-geth/cmd/celotool/main.go Celo/cmd/celotool/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..4397c43551e884832434982e167167471b38e384
--- /dev/null
+++ Celo/cmd/celotool/main.go
@@ -0,0 +1,41 @@
+// Copyright 2024 The celo Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/ethereum/go-ethereum/internal/flags"
+ "github.com/urfave/cli/v2"
+)
+
+var app *cli.App
+
+func init() {
+ app = flags.NewApp("Celo tool")
+ app.Commands = []*cli.Command{
+ commandSend,
+ }
+}
+
+func main() {
+ if err := app.Run(os.Args); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
diff --git op-geth/cmd/celotool/send-tx.go Celo/cmd/celotool/send-tx.go
new file mode 100644
index 0000000000000000000000000000000000000000..d72003f8a17b8f6300f2e6bca2bfc182f721f218
--- /dev/null
+++ Celo/cmd/celotool/send-tx.go
@@ -0,0 +1,141 @@
+// Copyright 2024 The celo Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/urfave/cli/v2"
+)
+
+var (
+ rpcUrlFlag = &cli.StringFlag{
+ Name: "rpc-url",
+ Aliases: []string{"r"},
+ DefaultText: "http://localhost:8545",
+ Usage: "The rpc endpoint",
+ }
+ privKeyFlag = &cli.StringFlag{
+ Name: "private-key",
+ Usage: "Use the provided private key",
+ Required: true,
+ }
+ valueFlag = &cli.Int64Flag{
+ Name: "value",
+ DefaultText: "0",
+ Usage: "The value to send, in wei",
+ }
+)
+
+var commandSend = &cli.Command{
+ Name: "send",
+ Usage: "send celo tx (cip-64)",
+ ArgsUsage: "[to] [feeCurrency]",
+ Description: `
+Send a CIP-64 transaction.
+
+- to: the address to send the transaction to, in hex format
+- feeCurrency: the fee currency address, in hex format
+
+Example:
+$ celotool send --rpc-url $RPC_URL --private-key $PRIVATE_KEY $TO $FEECURRENCY --value 1
+`,
+ Flags: []cli.Flag{
+ rpcUrlFlag,
+ privKeyFlag,
+ valueFlag,
+ },
+ Action: func(ctx *cli.Context) error {
+ privKeyRaw := ctx.String("private-key")
+ if len(privKeyRaw) >= 2 && privKeyRaw[0] == '0' && (privKeyRaw[1] == 'x' || privKeyRaw[1] == 'X') {
+ privKeyRaw = privKeyRaw[2:]
+ }
+ privateKey, err := crypto.HexToECDSA(privKeyRaw)
+ if err != nil {
+ return err
+ }
+
+ to := ctx.Args().Get(0)
+ if to == "" {
+ fmt.Println("missing 'to' address")
+ return nil
+ }
+ toAddress := common.HexToAddress(to)
+
+ feeCurrency := ctx.Args().Get(1)
+ if feeCurrency == "" {
+ fmt.Println("missing 'feeCurrency' address")
+ return nil
+ }
+ feeCurrencyAddress := common.HexToAddress(feeCurrency)
+
+ value := big.NewInt(ctx.Int64("value"))
+
+ rpcUrl := ctx.String("rpc-url")
+ client, err := ethclient.Dial(rpcUrl)
+ if err != nil {
+ return err
+ }
+
+ chainId, err := client.ChainID(context.Background())
+ if err != nil {
+ return fmt.Errorf("Can't get chain-id: %w", err)
+ }
+
+ nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(privateKey.PublicKey))
+ if err != nil {
+ return fmt.Errorf("Can't get pending nonce: %w", err)
+ }
+
+ feeCap, err := client.SuggestGasPriceForCurrency(context.Background(), &feeCurrencyAddress)
+ if err != nil {
+ return fmt.Errorf("Can't suggest gas price: %w", err)
+ }
+
+ txdata := &types.CeloDynamicFeeTxV2{
+ ChainID: chainId,
+ Nonce: nonce,
+ To: &toAddress,
+ Gas: 100_000,
+ GasFeeCap: feeCap,
+ GasTipCap: big.NewInt(2),
+ FeeCurrency: &feeCurrencyAddress,
+ Value: value,
+ }
+
+ signer := types.LatestSignerForChainID(chainId)
+ tx, err := types.SignNewTx(privateKey, signer, txdata)
+ if err != nil {
+ return fmt.Errorf("Can't sign tx: %w", err)
+ }
+
+ err = client.SendTransaction(context.Background(), tx)
+ if err != nil {
+ return fmt.Errorf("Can't send tx: %w", err)
+ }
+
+ fmt.Printf("tx sent: %s\n", tx.Hash().Hex())
+
+ return nil
+ },
+}
diff --git op-geth/cmd/evm/internal/t8ntool/execution.go Celo/cmd/evm/internal/t8ntool/execution.go
index 5fd1d6a4a6adfa13f712683bd7a8a15d8bd21dbb..7bce224ba0c01a4be57d29a57487ba2f091309e5 100644
--- op-geth/cmd/evm/internal/t8ntool/execution.go
+++ Celo/cmd/evm/internal/t8ntool/execution.go
@@ -219,7 +219,12 @@ log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", errMsg)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg})
continue
}
- msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee)
+ // NOTE: we can't provide exchange rates
+ // for fee-currencies here, since those are dynamically changing
+ // based on the oracle's exchange rates.
+ // When a Celo transaction with specified fee-currency is validated with this tool,
+ // this will thus result in a ErrUnregisteredFeeCurrency error for now.
+ msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee, vmContext.FeeCurrencyContext.ExchangeRates)
if err != nil {
log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
diff --git op-geth/cmd/evm/internal/t8ntool/transaction.go Celo/cmd/evm/internal/t8ntool/transaction.go
index 7f66ba4d85d69122049536e9a3d32934543d8231..9e63988d370040f6b755ca6647768bc9bba61d78 100644
--- op-geth/cmd/evm/internal/t8ntool/transaction.go
+++ Celo/cmd/evm/internal/t8ntool/transaction.go
@@ -133,8 +133,14 @@ } else {
r.Address = sender
}
// Check intrinsic gas
+ // NOTE: we can't provide specific intrinsic gas costs
+ // for fee-currencies here, since those are written to the
+ // FeeCurrencyDirectory contract and are chain-specific.
+ // When a Celo transaction with specified fee-currency is validated with this tool,
+ // this will thus result in a ErrUnregisteredFeeCurrency error for now.
+ var feeIntrinsic common.IntrinsicGasCosts
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
- chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil {
+ chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0), tx.FeeCurrency(), feeIntrinsic); err != nil {
r.Error = err
results = append(results, r)
continue
diff --git op-geth/cmd/geth/config.go Celo/cmd/geth/config.go
index df12a831b62fc21d4b73a6b56d50b82888b857de..486f06e4459fb5e8c3158339187ca4693a29e0be 100644
--- op-geth/cmd/geth/config.go
+++ Celo/cmd/geth/config.go
@@ -223,6 +223,11 @@ v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v
}
+ if ctx.IsSet(utils.OverrideOptimismCel2.Name) {
+ v := ctx.Uint64(utils.OverrideOptimismCel2.Name)
+ cfg.Eth.OverrideOptimismCel2 = &v
+ }
+
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information
diff --git op-geth/cmd/geth/main.go Celo/cmd/geth/main.go
index 30c7df3b84c347e116ddd3b74de1a9bd9dfc1c79..c698b12f4efc0cd5509813afb5c8fdef02dba6fe 100644
--- op-geth/cmd/geth/main.go
+++ Celo/cmd/geth/main.go
@@ -72,6 +72,7 @@ utils.OverrideOptimismFjord,
utils.OverrideOptimismGranite,
utils.OverrideOptimismHolocene,
utils.OverrideOptimismInterop,
+ utils.OverrideOptimismCel2,
utils.EnablePersonal,
utils.TxPoolLocalsFlag,
utils.TxPoolNoLocalsFlag,
@@ -131,6 +132,8 @@ utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerPendingFeeRecipientFlag,
utils.MinerNewPayloadTimeoutFlag, // deprecated
+ utils.CeloFeeCurrencyDefault,
+ utils.CeloFeeCurrencyLimits,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV4Flag,
diff --git op-geth/cmd/utils/flags.go Celo/cmd/utils/flags.go
index 8eda534783632a780823c576fd0389dbdddd8148..e935772b62a83c240cb14d8947625eb8ca6d33e2 100644
--- op-geth/cmd/utils/flags.go
+++ Celo/cmd/utils/flags.go
@@ -289,6 +289,11 @@ Name: "override.interop",
Usage: "Manually specify the Optimsim Interop feature-set fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
+ OverrideOptimismCel2 = &flags.BigFlag{
+ Name: "override.cel2",
+ Usage: "Manually specify the OptimismCel2 fork timestamp, overriding the bundled setting",
+ Category: flags.EthCategory,
+ }
SyncModeFlag = &flags.TextMarshalerFlag{
Name: "syncmode",
Usage: `Blockchain sync mode ("snap" or "full")`,
@@ -551,6 +556,17 @@ }
MinerPendingFeeRecipientFlag = &cli.StringFlag{
Name: "miner.pending.feeRecipient",
Usage: "0x prefixed public address for the pending block producer (not used for actual block production)",
+ Category: flags.MinerCategory,
+ }
+ CeloFeeCurrencyDefault = &cli.Float64Flag{
+ Name: "celo.feecurrency.default",
+ Usage: "Default fraction of block gas limit available for TXs paid with a whitelisted alternative currency",
+ Value: ethconfig.Defaults.Miner.FeeCurrencyDefault,
+ Category: flags.MinerCategory,
+ }
+ CeloFeeCurrencyLimits = &cli.StringFlag{
+ Name: "celo.feecurrency.limits",
+ Usage: "Comma separated currency address-to-block percentage mappings (<address>=<fraction>)",
Category: flags.MinerCategory,
}
@@ -1724,6 +1740,39 @@ cfg.RollupComputePendingBlock = ctx.Bool(RollupComputePendingBlock.Name)
}
}
+func setCeloMiner(ctx *cli.Context, cfg *miner.Config, networkId uint64) {
+ cfg.FeeCurrencyDefault = ctx.Float64(CeloFeeCurrencyDefault.Name)
+
+ defaultLimits, ok := miner.DefaultFeeCurrencyLimits[networkId]
+ if !ok {
+ defaultLimits = make(map[common.Address]float64)
+ }
+
+ cfg.FeeCurrencyLimits = defaultLimits
+
+ if ctx.IsSet(CeloFeeCurrencyLimits.Name) {
+ feeCurrencyLimits := ctx.String(CeloFeeCurrencyLimits.Name)
+
+ for _, entry := range strings.Split(feeCurrencyLimits, ",") {
+ parts := strings.Split(entry, "=")
+ if len(parts) != 2 {
+ Fatalf("Invalid fee currency limits entry: %s", entry)
+ }
+ var address common.Address
+ if err := address.UnmarshalText([]byte(parts[0])); err != nil {
+ Fatalf("Invalid fee currency address hash %s: %v", parts[0], err)
+ }
+
+ fraction, err := strconv.ParseFloat(parts[1], 64)
+ if err != nil {
+ Fatalf("Invalid block limit fraction %s: %v", parts[1], err)
+ }
+
+ cfg.FeeCurrencyLimits[address] = fraction
+ }
+ }
+}
+
func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
requiredBlocks := ctx.String(EthRequiredBlocksFlag.Name)
if requiredBlocks == "" {
@@ -2094,6 +2143,8 @@ cfg.VMTrace = name
cfg.VMTraceJsonConfig = config
}
}
+
+ setCeloMiner(ctx, &cfg.Miner, cfg.NetworkId)
}
// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
diff --git op-geth/common/celo_types.go Celo/common/celo_types.go
new file mode 100644
index 0000000000000000000000000000000000000000..8bbb341a2710b19a93746edd12b56988f8184c85
--- /dev/null
+++ Celo/common/celo_types.go
@@ -0,0 +1,118 @@
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "math/big"
+)
+
+var (
+ ZeroAddress = BytesToAddress([]byte{})
+)
+
+type AddressSet map[Address]struct{}
+
+type ExchangeRates = map[Address]*big.Rat
+type IntrinsicGasCosts = map[Address]uint64
+
+type FeeCurrencyContext struct {
+ ExchangeRates ExchangeRates
+ IntrinsicGasCosts IntrinsicGasCosts
+}
+
+// Only used in tracer tests
+func (fc *FeeCurrencyContext) UnmarshalJSON(data []byte) error {
+ var raw struct {
+ ExchangeRates map[Address][]json.Number `json:"exchangeRates"`
+ IntrinsicGasCosts map[Address]uint64 `json:"intrinsicGasCosts"`
+ }
+
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return err
+ }
+
+ fc.ExchangeRates = make(ExchangeRates)
+ for addr, rateArr := range raw.ExchangeRates {
+ if len(rateArr) != 2 {
+ return fmt.Errorf("invalid exchange rate array for address %s: expected 2 elements, got %d", addr, len(rateArr))
+ }
+ numerator, ok := new(big.Int).SetString(string(rateArr[0]), 10)
+ if !ok {
+ return fmt.Errorf("invalid numerator for address %s: %s", addr, rateArr[0])
+ }
+ denominator, ok := new(big.Int).SetString(string(rateArr[1]), 10)
+ if !ok {
+ return fmt.Errorf("invalid denominator for address %s: %s", addr, rateArr[1])
+ }
+
+ rate := new(big.Rat).SetFrac(numerator, denominator)
+ fc.ExchangeRates[addr] = rate
+ }
+ fc.IntrinsicGasCosts = raw.IntrinsicGasCosts
+
+ return nil
+}
+
+func NewAddressSet(addresses ...Address) AddressSet {
+ as := AddressSet{}
+ for _, address := range addresses {
+ as[address] = struct{}{}
+ }
+ return as
+}
+
+func MaxAllowedIntrinsicGasCost(i IntrinsicGasCosts, feeCurrency *Address) (uint64, bool) {
+ intrinsicGas, ok := CurrencyIntrinsicGasCost(i, feeCurrency)
+ if !ok {
+ return 0, false
+ }
+ // Allow the contract to overshoot 2 times the deducted intrinsic gas
+ // during execution.
+ // If the feeCurrency is nil, then the max allowed intrinsic gas cost
+ // is 0 (i.e. not allowed) for a fee-currency specific EVM call within the STF.
+ return intrinsicGas * 3, true
+}
+
+func CurrencyIntrinsicGasCost(i IntrinsicGasCosts, feeCurrency *Address) (uint64, bool) {
+ // the additional intrinsic gas cost for a non fee-currency
+ // transaction is 0
+ if feeCurrency == nil {
+ return 0, true
+ }
+ gasCost, ok := i[*feeCurrency]
+ if !ok {
+ return 0, false
+ }
+ return gasCost, true
+}
+
+func CurrencyAllowlist(exchangeRates ExchangeRates) AddressSet {
+ addrs := AddressSet{}
+ for k := range exchangeRates {
+ addrs[k] = struct{}{}
+ }
+ return addrs
+}
+
+func IsCurrencyAllowed(exchangeRates ExchangeRates, feeCurrency *Address) bool {
+ if feeCurrency == nil {
+ return true
+ }
+
+ // Check if fee currency is registered
+ _, ok := exchangeRates[*feeCurrency]
+ return ok
+}
+
+func AreSameAddress(a, b *Address) bool {
+ // both are nil or point to the same address
+ if a == b {
+ return true
+ }
+ // if only one is nil
+ if a == nil || b == nil {
+ return false
+ }
+ // if they point to the same
+ return *a == *b
+}
diff --git op-geth/common/celo_types_test.go Celo/common/celo_types_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4db38cdf1461834fe78e34a867749662b0ccaf58
--- /dev/null
+++ Celo/common/celo_types_test.go
@@ -0,0 +1,47 @@
+package common
+
+import (
+ "math/big"
+ "testing"
+)
+
+var (
+ currA = HexToAddress("0xA")
+ currB = HexToAddress("0xB")
+ currX = HexToAddress("0xF")
+ exchangeRates = ExchangeRates{
+ currA: big.NewRat(47, 100),
+ currB: big.NewRat(45, 100),
+ }
+)
+
+func TestIsCurrencyAllowed(t *testing.T) {
+ tests := []struct {
+ name string
+ feeCurrency *Address
+ want bool
+ }{
+ {
+ name: "no fee currency",
+ feeCurrency: nil,
+ want: true,
+ },
+ {
+ name: "valid fee currency",
+ feeCurrency: &currA,
+ want: true,
+ },
+ {
+ name: "invalid fee currency",
+ feeCurrency: &currX,
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := IsCurrencyAllowed(exchangeRates, tt.feeCurrency); got != tt.want {
+ t.Errorf("IsCurrencyAllowed() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git op-geth/common/exchange/rates.go Celo/common/exchange/rates.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c308eb96435a2923b626f0f28b4c01ac4246ceb
--- /dev/null
+++ Celo/common/exchange/rates.go
@@ -0,0 +1,168 @@
+package exchange
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+var (
+ unitRate = big.NewRat(1, 1)
+ // ErrUnregisteredFeeCurrency is returned if the currency specified to use for the fees
+ // isn't one of the currencies whitelisted for that purpose.
+ ErrUnregisteredFeeCurrency = errors.New("unregistered fee-currency address")
+)
+
+// ConvertCurrency does an exchange conversion from currencyFrom to currencyTo of the value given.
+func ConvertCurrency(exchangeRates common.ExchangeRates, val1 *big.Int, currencyFrom *common.Address, currencyTo *common.Address) *big.Int {
+ celoAmount, err := ConvertCurrencyToCelo(exchangeRates, currencyFrom, val1)
+ if err != nil {
+ log.Error("Error trying to convert from currency to CELO.", "value", val1, "fromCurrency", currencyFrom.Hex())
+ }
+ toAmount, err := ConvertCeloToCurrency(exchangeRates, currencyTo, celoAmount)
+ if err != nil {
+ log.Error("Error trying to convert from CELO to currency.", "value", celoAmount, "toCurrency", currencyTo.Hex())
+ }
+ return toAmount
+}
+
+func ConvertCurrencyToCelo(exchangeRates common.ExchangeRates, feeCurrency *common.Address, currencyAmount *big.Int) (*big.Int, error) {
+ if feeCurrency == nil {
+ return currencyAmount, nil
+ }
+ if currencyAmount == nil {
+ return nil, fmt.Errorf("could not convert nil amount to CELO")
+ }
+ exchangeRate, ok := exchangeRates[*feeCurrency]
+ if !ok {
+ return nil, fmt.Errorf("could not convert from fee currency to native (fee-currency=%s): %w ", feeCurrency, ErrUnregisteredFeeCurrency)
+ }
+ return new(big.Int).Div(new(big.Int).Mul(currencyAmount, exchangeRate.Denom()), exchangeRate.Num()), nil
+}
+
+func ConvertCeloToCurrency(exchangeRates common.ExchangeRates, feeCurrency *common.Address, celoAmount *big.Int) (*big.Int, error) {
+ if feeCurrency == nil {
+ return celoAmount, nil
+ }
+ if celoAmount == nil {
+ return nil, fmt.Errorf("Can't convert nil amount to fee currency.")
+ }
+ exchangeRate, ok := exchangeRates[*feeCurrency]
+ if !ok {
+ return nil, fmt.Errorf("could not convert from native to fee currency (fee-currency=%s): %w ", feeCurrency, ErrUnregisteredFeeCurrency)
+ }
+ return new(big.Int).Div(new(big.Int).Mul(celoAmount, exchangeRate.Num()), exchangeRate.Denom()), nil
+}
+
+func getRate(exchangeRates common.ExchangeRates, feeCurrency *common.Address) (*big.Rat, error) {
+ if feeCurrency == nil {
+ return unitRate, nil
+ }
+ rate, ok := exchangeRates[*feeCurrency]
+ if !ok {
+ return nil, fmt.Errorf("fee currency not registered: %s", feeCurrency.Hex())
+ }
+ return rate, nil
+}
+
+// CompareValue compares values in different currencies (nil currency is native currency)
+// returns -1 0 or 1 depending if val1 < val2, val1 == val2, or val1 > val2 respectively.
+func CompareValue(exchangeRates common.ExchangeRates, val1 *big.Int, feeCurrency1 *common.Address, val2 *big.Int, feeCurrency2 *common.Address) (int, error) {
+ // Short circuit if the fee currency is the same.
+ if common.AreSameAddress(feeCurrency1, feeCurrency2) {
+ return val1.Cmp(val2), nil
+ }
+
+ exchangeRate1, err := getRate(exchangeRates, feeCurrency1)
+ if err != nil {
+ return 0, err
+ }
+ exchangeRate2, err := getRate(exchangeRates, feeCurrency2)
+ if err != nil {
+ return 0, err
+ }
+
+ // Below code block is basically evaluating this comparison:
+ // val1 * exchangeRate1.denominator / exchangeRate1.numerator < val2 * exchangeRate2.denominator / exchangeRate2.numerator
+ // It will transform that comparison to this, to remove having to deal with fractional values.
+ // val1 * exchangeRate1.denominator * exchangeRate2.numerator < val2 * exchangeRate2.denominator * exchangeRate1.numerator
+ leftSide := new(big.Int).Mul(
+ val1,
+ new(big.Int).Mul(
+ exchangeRate1.Denom(),
+ exchangeRate2.Num(),
+ ),
+ )
+ rightSide := new(big.Int).Mul(
+ val2,
+ new(big.Int).Mul(
+ exchangeRate2.Denom(),
+ exchangeRate1.Num(),
+ ),
+ )
+
+ return leftSide.Cmp(rightSide), nil
+}
+
+// RatesAndFees holds exchange rates and the basefees expressed in the rates currencies.
+type RatesAndFees struct {
+ Rates common.ExchangeRates
+
+ nativeBaseFee *big.Int
+ currencyBaseFees map[common.Address]*big.Int
+}
+
+// NewRatesAndFees creates a new empty RatesAndFees object.
+func NewRatesAndFees(rates common.ExchangeRates, nativeBaseFee *big.Int) *RatesAndFees {
+ // While it could be made so that currency basefees are calculated on demand,
+ // the low amount of these (usually N < 20)
+ return &RatesAndFees{
+ Rates: rates,
+ nativeBaseFee: nativeBaseFee,
+ currencyBaseFees: make(map[common.Address]*big.Int, len(rates)),
+ }
+}
+
+// HasBaseFee returns if the basefee is set.
+func (rf *RatesAndFees) HasBaseFee() bool {
+ return rf.nativeBaseFee != nil
+}
+
+// GetNativeBaseFee returns the basefee in celo currency.
+func (rf *RatesAndFees) GetNativeBaseFee() *big.Int {
+ return rf.nativeBaseFee
+}
+
+// GetBaseFeeIn returns the basefee expressed in the specified currency. Returns nil
+// if the currency is not allowlisted.
+func (rf *RatesAndFees) GetBaseFeeIn(currency *common.Address) *big.Int {
+ // If native currency is being requested, return it
+ if currency == nil {
+ return rf.nativeBaseFee
+ }
+ // If a non-native currency is being requested, but it is nil,
+ // it means there is no baseFee in this context. Return nil as well.
+ if rf.nativeBaseFee == nil {
+ return nil
+ }
+ // Check the cache
+ baseFee, ok := rf.currencyBaseFees[*currency]
+ if ok {
+ return baseFee
+ }
+ // Not found, calculate
+ calculatedBaseFee, err := ConvertCeloToCurrency(rf.Rates, currency, rf.nativeBaseFee)
+ if err != nil {
+ // Should never happen: error lvl log line
+ log.Error("BaseFee requested for unregistered currency",
+ "currency", currency.Hex(),
+ "exchangeRates", rf.Rates,
+ "cause", err)
+ return nil
+ }
+ rf.currencyBaseFees[*currency] = calculatedBaseFee
+ return calculatedBaseFee
+}
diff --git op-geth/common/exchange/rates_test.go Celo/common/exchange/rates_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3cda8eb530ff2a9df75aebad4619d76c52aab558
--- /dev/null
+++ Celo/common/exchange/rates_test.go
@@ -0,0 +1,171 @@
+package exchange
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var (
+ currA = common.HexToAddress("0xA")
+ currB = common.HexToAddress("0xB")
+ currX = common.HexToAddress("0xF")
+ exchangeRates = common.ExchangeRates{
+ currA: big.NewRat(47, 100),
+ currB: big.NewRat(45, 100),
+ }
+)
+
+func TestCompareFees(t *testing.T) {
+ type args struct {
+ val1 *big.Int
+ feeCurrency1 *common.Address
+ val2 *big.Int
+ feeCurrency2 *common.Address
+ }
+ tests := []struct {
+ name string
+ args args
+ wantResult int
+ wantErr bool
+ }{
+ // Native currency
+ {
+ name: "Same amount of native currency",
+ args: args{
+ val1: big.NewInt(1),
+ feeCurrency1: nil,
+ val2: big.NewInt(1),
+ feeCurrency2: nil,
+ },
+ wantResult: 0,
+ }, {
+ name: "Different amounts of native currency 1",
+ args: args{
+ val1: big.NewInt(2),
+ feeCurrency1: nil,
+ val2: big.NewInt(1),
+ feeCurrency2: nil,
+ },
+ wantResult: 1,
+ }, {
+ name: "Different amounts of native currency 2",
+ args: args{
+ val1: big.NewInt(1),
+ feeCurrency1: nil,
+ val2: big.NewInt(5),
+ feeCurrency2: nil,
+ },
+ wantResult: -1,
+ },
+ // Mixed currency
+ {
+ name: "Same amount of mixed currency",
+ args: args{
+ val1: big.NewInt(1),
+ feeCurrency1: nil,
+ val2: big.NewInt(1),
+ feeCurrency2: &currA,
+ },
+ wantResult: -1,
+ }, {
+ name: "Different amounts of mixed currency 1",
+ args: args{
+ val1: big.NewInt(100),
+ feeCurrency1: nil,
+ val2: big.NewInt(47),
+ feeCurrency2: &currA,
+ },
+ wantResult: 0,
+ }, {
+ name: "Different amounts of mixed currency 2",
+ args: args{
+ val1: big.NewInt(45),
+ feeCurrency1: &currB,
+ val2: big.NewInt(100),
+ feeCurrency2: nil,
+ },
+ wantResult: 0,
+ },
+ // Two fee currencies
+ {
+ name: "Same amount of same currency",
+ args: args{
+ val1: big.NewInt(1),
+ feeCurrency1: &currA,
+ val2: big.NewInt(1),
+ feeCurrency2: &currA,
+ },
+ wantResult: 0,
+ }, {
+ name: "Different amounts of same currency 1",
+ args: args{
+ val1: big.NewInt(3),
+ feeCurrency1: &currA,
+ val2: big.NewInt(1),
+ feeCurrency2: &currA,
+ },
+ wantResult: 1,
+ }, {
+ name: "Different amounts of same currency 2",
+ args: args{
+ val1: big.NewInt(1),
+ feeCurrency1: &currA,
+ val2: big.NewInt(7),
+ feeCurrency2: &currA,
+ },
+ wantResult: -1,
+ }, {
+ name: "Different amounts of different currencies 1",
+ args: args{
+ val1: big.NewInt(47),
+ feeCurrency1: &currA,
+ val2: big.NewInt(45),
+ feeCurrency2: &currB,
+ },
+ wantResult: 0,
+ }, {
+ name: "Different amounts of different currencies 2",
+ args: args{
+ val1: big.NewInt(48),
+ feeCurrency1: &currA,
+ val2: big.NewInt(45),
+ feeCurrency2: &currB,
+ },
+ wantResult: 1,
+ }, {
+ name: "Different amounts of different currencies 3",
+ args: args{
+ val1: big.NewInt(47),
+ feeCurrency1: &currA,
+ val2: big.NewInt(46),
+ feeCurrency2: &currB,
+ },
+ wantResult: -1,
+ },
+ // Unregistered fee currency
+ {
+ name: "Different amounts of different currencies",
+ args: args{
+ val1: big.NewInt(1),
+ feeCurrency1: &currA,
+ val2: big.NewInt(1),
+ feeCurrency2: &currX,
+ },
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := CompareValue(exchangeRates, tt.args.val1, tt.args.feeCurrency1, tt.args.val2, tt.args.feeCurrency2)
+
+ if tt.wantErr && err == nil {
+ t.Error("Expected error in CompareValue()")
+ }
+ if got != tt.wantResult {
+ t.Errorf("CompareValue() = %v, want %v", got, tt.wantResult)
+ }
+ })
+ }
+}
diff --git op-geth/compat_test/compat_test.go Celo/compat_test/compat_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..92b63cb33674b47aae07fa1c87029dc83c077aec
--- /dev/null
+++ Celo/compat_test/compat_test.go
@@ -0,0 +1,999 @@
+//go:build compat_test
+
+package compat_tests
+
+import (
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "math/big"
+ "runtime"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/davecgh/go-spew/spew"
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/sync/errgroup"
+)
+
+// Note! That although it would be great to be able to compare the output of the
+// respective celo and op-geth ethclient.Clients, unfortunately we are not able
+// to pull in the celo client as a dependency since the cgo compilation results
+// in duplicate symbol errors in the secp256k1 c code. E.g.:
+//
+// duplicate symbol '_secp256k1_ec_pubkey_tweak_mul' in: ...
+//
+// There are 33 such instances and it doesn't seem trivial to resolve. So we
+// content ourselves with just calling the rpc endpoints of the celo node
+// using the op-geth rpc client.
+
+var (
+ celoRpcURL string
+ opGethRpcURL string
+ startBlock uint64
+ gingerbreadBlocks = map[uint64]uint64{params.CeloMainnetChainID: 21616000, params.CeloBaklavaChainID: 18785000, params.CeloAlfajoresChainID: 19814000}
+)
+
+func init() {
+ // Define your custom flag
+ flag.StringVar(&celoRpcURL, "celo-url", "", "celo rpc url")
+ flag.StringVar(&opGethRpcURL, "op-geth-url", "", "op-geth rpc url")
+ flag.Uint64Var(&startBlock, "start-block", 0, "the block to start at")
+}
+
+type clients struct {
+ celoEthclient, opEthclient *ethclient.Client
+ celoClient, opClient *rpc.Client
+}
+
+// Connects to a celo-blockchain and an op-geth node over rpc, selects the lowest head block and iterates from 0 to the
+// lowest head block comparing all blocks, transactions receipts and logs.
+//
+// The test requires two flags to be set that provide the rpc urls to use, and the test is segregated from normal
+// execution via a build tag. So to run it you would do:
+//
+// go test -v ./compat_test -tags compat_test -celo-url <celo rpc url> -op-geth-url <op-geth rpc url>
+func TestCompatibilityOfChains(t *testing.T) {
+ flag.Parse()
+
+ if celoRpcURL == "" {
+ t.Fatal("celo rpc url not set example usage:\n go test -v ./compat_test -tags compat_test -celo-url ws://localhost:9546 -op-geth-url ws://localhost:8546")
+ }
+ if opGethRpcURL == "" {
+ t.Fatal("op-geth rpc url not set example usage:\n go test -v ./compat_test -tags compat_test -celo-url ws://localhost:9546 -op-geth-url ws://localhost:8546")
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
+ defer cancel()
+
+ clientOpts := []rpc.ClientOption{rpc.WithWebsocketMessageSizeLimit(1024 * 1024 * 256)}
+
+ celoClient, err := rpc.DialOptions(context.Background(), celoRpcURL, clientOpts...)
+ require.NoError(t, err)
+ celoEthClient := ethclient.NewClient(celoClient)
+
+ opClient, err := rpc.DialOptions(context.Background(), opGethRpcURL, clientOpts...)
+ require.NoError(t, err)
+ opEthClient := ethclient.NewClient(opClient)
+
+ clients := &clients{
+ celoEthclient: celoEthClient,
+ opEthclient: opEthClient,
+ celoClient: celoClient,
+ opClient: opClient,
+ }
+
+ celoChainID, err := celoEthClient.ChainID(ctx)
+ require.NoError(t, err)
+ opChainID, err := opEthClient.ChainID(ctx)
+ require.NoError(t, err)
+ require.Equal(t, celoChainID.Uint64(), opChainID.Uint64(), "chain ids of referenced chains differ")
+
+ _, ok := gingerbreadBlocks[celoChainID.Uint64()]
+ require.True(t, ok, "chain id %d not found in supported chainIDs %v", celoChainID.Uint64(), gingerbreadBlocks)
+
+ latestCeloBlock, err := celoEthClient.BlockNumber(ctx)
+ require.NoError(t, err)
+
+ latestOpBlock, err := opEthClient.BlockNumber(ctx)
+ require.NoError(t, err)
+
+ // We take the lowest of the two blocks
+ latestBlock := latestCeloBlock
+ if latestOpBlock < latestCeloBlock {
+ latestBlock = latestOpBlock
+ }
+ // We subtract 128 from the latest block to avoid handlig blocks where state
+ // is present in celo since when state is present baseFeePerGas is set on
+ // the celo block with a value, and we can't access that state from the
+ // op-geth side
+ endBlock := latestBlock - 128
+ batches := make(map[uint64]*batch)
+ fmt.Printf("start block: %v, end block: %v\n", startBlock, endBlock)
+ start := time.Now()
+ prev := start
+ var batchSize uint64 = 1000
+ var count uint64
+ resultChan := make(chan *blockResults, 100)
+
+ longCtx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ g, longCtx := errgroup.WithContext(longCtx)
+ g.SetLimit(5)
+
+ g.Go(func() error {
+ for i := startBlock; i <= endBlock; i++ {
+ index := i
+ g.Go(func() error {
+ err := fetchBlockElements(longCtx, clients, index, resultChan)
+ if err != nil {
+ fmt.Printf("block %d err: %v\n", index, err)
+ }
+ return err
+ })
+ select {
+ case <-longCtx.Done():
+ return longCtx.Err()
+ default:
+ }
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ // Set up contiguous block tracking
+ latestContiguousBlock := startBlock
+ if startBlock > 0 {
+ latestContiguousBlock = startBlock - 1
+ }
+ receivedNumbers := make(map[uint64]bool)
+ for {
+ select {
+ case <-longCtx.Done():
+ return longCtx.Err()
+ case blockResult := <-resultChan:
+
+ err := blockResult.Verify(celoChainID.Uint64())
+ if err != nil {
+ return fmt.Errorf("block verification failed: %w, failureBlock: %d ,latestContiguousBlock: %d", err, blockResult.blockNumber, latestContiguousBlock)
+ }
+
+ receivedNumbers[blockResult.blockNumber] = true
+ for receivedNumbers[latestContiguousBlock+1] {
+ delete(receivedNumbers, latestContiguousBlock)
+ latestContiguousBlock++
+ }
+
+ // get the batch
+ batchIndex := blockResult.blockNumber / batchSize
+ b, ok := batches[batchIndex]
+ if !ok {
+ batchStart := batchIndex * batchSize
+ b = newBatch(batchStart, batchStart+batchSize, clients.celoEthclient, clients.opEthclient)
+ batches[batchIndex] = b
+ }
+ done, err := b.Process(blockResult)
+ if err != nil {
+ err := fmt.Errorf("batch %d procesing failed: %w, latestContiguousBlock %d", batchIndex, err, latestContiguousBlock)
+ fmt.Printf("%v\n", err)
+ return err
+ }
+ if done {
+ delete(batches, batchIndex)
+ }
+
+ count++
+ if count%batchSize == 0 {
+ fmt.Printf("buffered objects %d\t\t current goroutines %v\t\tblocks %d\t\tlatestContiguous %d\t\telapsed: %v\ttotal: %v\n", len(resultChan), runtime.NumGoroutine(), count, latestContiguousBlock, time.Since(prev), time.Since(start))
+ prev = time.Now()
+ }
+ // Check to see if we have processed all blocks
+ if count == endBlock-startBlock+1 {
+ fmt.Printf("buffered objects %d\t\t current goroutines %v\t\tblocks %d\t\tlatestContiguous %d\t\telapsed: %v\ttotal: %v\n", len(resultChan), runtime.NumGoroutine(), count, latestContiguousBlock, time.Since(prev), time.Since(start))
+ return nil
+ }
+ }
+ }
+ })
+
+ require.NoError(t, g.Wait())
+}
+
+type batch struct {
+ start, end uint64
+ remaining uint64
+ incrementalLogs [][]*types.Log
+ celoClient, opClient *ethclient.Client
+ celoRawLogs, opRawLogs []json.RawMessage
+ celoLogs, opLogs []*types.Log
+ logFetchErrGroup *errgroup.Group
+ logFetchContext context.Context
+}
+
+func newBatch(start, end uint64, celoClient, opClient *ethclient.Client) *batch {
+ // We discard the cancel func because the errgroup will cancel the context.
+ ctx, _ := context.WithTimeout(context.Background(), time.Minute*5)
+ g, ctx := errgroup.WithContext(ctx)
+ b := &batch{
+ start: start,
+ end: end,
+ remaining: end - start,
+ incrementalLogs: make([][]*types.Log, end-start),
+ celoClient: celoClient,
+ opClient: opClient,
+ logFetchErrGroup: g,
+ logFetchContext: ctx,
+ }
+ b.fetchLogs()
+ return b
+}
+
+func (b *batch) fetchLogs() {
+ query := ethereum.FilterQuery{
+ FromBlock: big.NewInt(int64(b.start)),
+ ToBlock: big.NewInt(int64(b.end - 1)),
+ }
+ rawQuery := filterQuery{
+ FromBlock: hexutil.Uint64(b.start),
+ ToBlock: hexutil.Uint64(b.end - 1),
+ }
+ b.logFetchErrGroup.Go(func() error {
+ logs, err := b.celoClient.FilterLogs(b.logFetchContext, query)
+ if err != nil {
+ return err
+ }
+ logPointers := make([]*types.Log, len(logs))
+ for i, log := range logs {
+ logCopy := log
+ logPointers[i] = &logCopy
+ }
+ b.celoLogs = logPointers
+ return nil
+ })
+ b.logFetchErrGroup.Go(func() error {
+ return rpcCall(b.logFetchContext, b.celoClient.Client(), &b.celoRawLogs, "eth_getLogs", rawQuery)
+ })
+ b.logFetchErrGroup.Go(func() error {
+ logs, err := b.opClient.FilterLogs(b.logFetchContext, query)
+ if err != nil {
+ return err
+ }
+ logPointers := make([]*types.Log, len(logs))
+ for i, log := range logs {
+ logCopy := log
+ logPointers[i] = &logCopy
+ }
+ b.opLogs = logPointers
+ return nil
+ })
+ b.logFetchErrGroup.Go(func() error {
+ return rpcCall(b.logFetchContext, b.opClient.Client(), &b.opRawLogs, "eth_getLogs", rawQuery)
+ })
+}
+
+func (b *batch) Process(results *blockResults) (done bool, err error) {
+ logs, err := results.Logs()
+ if err != nil {
+ return false, err
+ }
+ b.incrementalLogs[results.blockNumber-b.start] = logs
+ b.remaining--
+ if b.remaining == 0 {
+ // copy all incremental logs into one slice of type []*types.Log
+ allLogs := make([]*types.Log, 0)
+ for _, logs := range b.incrementalLogs {
+ allLogs = append(allLogs, logs...)
+ }
+
+ err = b.logFetchErrGroup.Wait()
+ if err != nil {
+ return false, err
+ }
+
+ err = EqualObjects(b.celoLogs, b.opLogs)
+ if err != nil {
+ return false, err
+ }
+ err = EqualObjects(b.celoRawLogs, b.opRawLogs)
+ if err != nil {
+ return false, err
+ }
+
+ // crosscheck the logs
+ err = EqualObjects(len(b.celoLogs), len(allLogs))
+ if err != nil {
+ return false, err
+ }
+ err = EqualObjects(b.celoLogs, allLogs)
+ if err != nil {
+ return false, err
+ }
+
+ var unmarshaledCeloRawLogs []*types.Log
+ err = jsonConvert(b.celoRawLogs, &unmarshaledCeloRawLogs)
+ if err != nil {
+ return false, err
+ }
+
+ err = EqualObjects(b.celoLogs, unmarshaledCeloRawLogs)
+ if err != nil {
+ return false, err
+ }
+ }
+ return b.remaining == 0, nil
+}
+
+// Holds results retrieved from the celo and op-geth clients rpc api, fields named raw were retrieved via a direct rpc
+// call whereas fields without raw were retrieved via the ethclient.
+type blockResults struct {
+ blockNumber uint64
+ // danglingState bool
+
+ // Blocks
+ opBlockByNumber *types.Block
+ opBlockByHash *types.Block
+ celoRawBlockByNumber map[string]interface{}
+ opRawBlockByNumber map[string]interface{}
+ celoRawBlockByHash map[string]interface{}
+ opRawBlockByHash map[string]interface{}
+
+ // Transactions
+ celoTxs []*types.Transaction
+ opTxs []*types.Transaction
+ celoRawTxs []map[string]interface{}
+ opRawTxs []map[string]interface{}
+
+ // Receipts
+ celoReceipts []*types.Receipt
+ opReceipts []*types.Receipt
+ celoRawReceipts []map[string]interface{}
+ opRawReceipts []map[string]interface{}
+
+ // BlockReceipts
+ celoBlockReceipts []*types.Receipt
+ opBlockReceipts []*types.Receipt
+ celoRawBlockReceipts []map[string]interface{}
+ opRawBlockReceipts []map[string]interface{}
+
+ // Block receipt (special receipt added by celo to capture system operations)
+ celoRawBlockReceipt map[string]interface{}
+ opRawBlockReceipt map[string]interface{}
+}
+
+func (r *blockResults) verifyBlocks(chainID uint64) error {
+ // Check block pairs
+ makeBlockComparable(r.opBlockByNumber)
+ makeBlockComparable(r.opBlockByHash)
+ // Optimism blocks via ethclient
+ err := EqualObjects(r.opBlockByNumber, r.opBlockByHash)
+ if err != nil {
+ return err
+ }
+
+ // Raw blocks by number
+ err = filterCeloBlock(r.blockNumber, r.celoRawBlockByNumber, gingerbreadBlocks[chainID])
+ if err != nil {
+ return err
+ }
+ err = filterOpBlock(r.opRawBlockByNumber)
+ if err != nil {
+ return err
+ }
+ err = EqualObjects(r.celoRawBlockByNumber, r.opRawBlockByNumber)
+ if err != nil {
+ return err
+ }
+
+ // Raw blocks by hash
+ err = filterCeloBlock(r.blockNumber, r.celoRawBlockByHash, gingerbreadBlocks[chainID])
+ if err != nil {
+ return err
+ }
+ err = filterOpBlock(r.opRawBlockByHash)
+ if err != nil {
+ return err
+ }
+ err = EqualObjects(r.celoRawBlockByHash, r.opRawBlockByHash)
+ if err != nil {
+ return err
+ }
+
+ // Cross check
+ err = EqualObjects(r.celoRawBlockByNumber, r.celoRawBlockByHash)
+ if err != nil {
+ return err
+ }
+
+ // We can't easily convert blocks from the ethclient to a map[string]interface{} since they lack hydrated fields and
+ // also due to the json conversion end up with null for unset fields (as opposed to just not having the field). So
+ // instead we compare the hashes.
+ err = EqualObjects(r.celoRawBlockByNumber["hash"].(string), r.opBlockByNumber.Hash().String())
+ if err != nil {
+ return err
+ }
+ celoRawTxs := r.celoRawBlockByNumber["transactions"].([]interface{})
+ err = EqualObjects(len(celoRawTxs), len(r.opBlockByNumber.Transactions()))
+ if err != nil {
+ return err
+ }
+ for i := range celoRawTxs {
+ celoTx := celoRawTxs[i].(map[string]interface{})
+ err = EqualObjects(celoTx["hash"].(string), r.opBlockByNumber.Transactions()[i].Hash().String())
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func filterOpBlock(block map[string]interface{}) error {
+ // We remove the following fields:
+ // size: being the size of the rlp encoded block it differs between the two systems since the block structure is different
+ // chainId (on transactions): celo didn't return chainId on legacy transactions, it did for other types but its a bit more involved to filter that per tx type.
+ //
+ // If the gas limit is zero (I.E. pre gingerbread) then we also remove the following fields:
+ // gasLimit: since on the celo side we hardcoded pre-gingerbread gas limits but on the op-geth side we just have zero.
+ // uncles, sha3Uncles, mixHash, nonce: since these are not present in the pre-gingerbread celo block.
+ delete(block, "size")
+ transactions, ok := block["transactions"].([]interface{})
+ if ok {
+ for _, tx := range transactions {
+ txMap, ok := tx.(map[string]interface{})
+ if ok {
+ filterOpTx(txMap)
+ }
+ }
+ }
+ gasLimit, ok := block["gasLimit"].(string)
+ if ok && gasLimit == "0x0" {
+ delete(block, "uncles")
+ delete(block, "sha3Uncles")
+ delete(block, "mixHash")
+ delete(block, "nonce")
+ delete(block, "gasLimit")
+ }
+ return nil
+}
+
+func filterCeloBlock(blockNumber uint64, block map[string]interface{}, gingerbreadBlock uint64) error {
+ // We remove the following fields:
+ // size: being the size of the rlp encoded block it differs between the two systems since the block structure is different
+ // randomness: we removed the concept of randomness for cel2 we filtered out the value in blocks during the migration.
+ // epochSnarkData: same as randomness
+ // gasLimit: removed for now since we don't have the value in the op-geth block so the op-geth block will just show 0, we may add this to op-geth later.
+ // chainId (on transactions): celo didn't return chainId on legacy transactions, it did for other types but its a bit more involved to filter that per tx type.
+
+ delete(block, "size")
+ delete(block, "randomness")
+ delete(block, "epochSnarkData")
+ if blockNumber < gingerbreadBlock {
+ // We hardcoded the gas limit in celo for pre-gingerbread blocks, we don't have that in op-geth so we remove it
+ // from the celo block.
+ delete(block, "gasLimit")
+ }
+ transactions, ok := block["transactions"].([]interface{})
+ if ok {
+ for _, tx := range transactions {
+ txMap := tx.(map[string]interface{})
+ filterCeloTx(txMap)
+ }
+ }
+
+ // We need to filter out the istanbulAggregatedSeal from the extra data, since that was also filtered out during the migration process.
+ extraData, ok := block["extraData"].(string)
+ if !ok {
+ return fmt.Errorf("extraData field not found or not a string in celo response")
+ }
+
+ extraDataBytes, err := hexutil.Decode(strings.TrimSpace(extraData))
+ if err != nil {
+ return fmt.Errorf("failed to hex decode extra data from celo response: %v", err)
+ }
+
+ if len(extraDataBytes) < IstanbulExtraVanity {
+ return fmt.Errorf("invalid istanbul header extra-data length from res1 expecting at least %d but got %d", IstanbulExtraVanity, len(extraDataBytes))
+ }
+
+ istanbulExtra := &IstanbulExtra{}
+ err = rlp.DecodeBytes(extraDataBytes[IstanbulExtraVanity:], istanbulExtra)
+ if err != nil {
+ return fmt.Errorf("failed to decode extra data from celo response: %v", err)
+ }
+
+ // Remove the istanbulAggregatedSeal from the extra data
+ istanbulExtra.AggregatedSeal = IstanbulAggregatedSeal{}
+
+ reEncodedExtra, err := rlp.EncodeToBytes(istanbulExtra)
+ if err != nil {
+ return fmt.Errorf("failed to re-encode extra data from celo response: %v", err)
+ }
+ finalEncodedString := hexutil.Encode(append(extraDataBytes[:IstanbulExtraVanity], reEncodedExtra...))
+
+ block["extraData"] = finalEncodedString
+
+ return nil
+}
+
+func (r *blockResults) verifyTransactions() error {
+ makeTransactionsComparable(r.celoTxs)
+ makeTransactionsComparable(r.opTxs)
+
+ // We also need to take into account yparity (only set on op transactions which can cause different representations for the v value)
+ // We overcome this by re-setting the v value of the celo txs to the op txs v value
+ for _, tx := range r.celoTxs {
+ // This sets the y value to be a big number where abs is nul rather than a zero length array if the number is zero.
+ // It doesn't change the number but it does change the representation.
+ types.SetYNullStyleBigIfZero(tx)
+ }
+ err := EqualObjects(r.celoTxs, r.opTxs)
+ if err != nil {
+ return err
+ }
+ // filter raw celo and op txs
+ for i := range r.celoRawTxs {
+ filterCeloTx(r.celoRawTxs[i])
+ }
+ for i := range r.opRawTxs {
+ filterOpTx(r.opRawTxs[i])
+ }
+ err = EqualObjects(r.celoRawTxs, r.opRawTxs)
+ if err != nil {
+ return err
+ }
+
+ // cross check txs, unfortunately we can't easily do a direct comparison here so we compare number of txs and their
+ // hashes.
+ err = EqualObjects(len(r.celoTxs), len(r.celoRawTxs))
+ if err != nil {
+ return err
+ }
+ for i := range r.celoTxs {
+ err = EqualObjects(r.celoTxs[i].Hash().String(), r.celoRawTxs[i]["hash"].(string))
+ if err != nil {
+ return err
+ }
+ }
+
+ // Cross check the individually retrieved transactions with the transactions in the block
+ return EqualObjects(r.celoTxs, []*types.Transaction(r.opBlockByNumber.Transactions()))
+}
+
+func (r *blockResults) verifyReceipts() error {
+ err := EqualObjects(r.celoReceipts, r.opReceipts)
+ if err != nil {
+ return err
+ }
+
+ // filter the raw op receipts
+ for i := range r.opReceipts {
+ filterOpReceipt(r.opRawReceipts[i])
+ }
+ err = EqualObjects(len(r.celoRawReceipts), len(r.opRawReceipts))
+ if err != nil {
+ return err
+ }
+ for i := range r.celoRawReceipts {
+ err = EqualObjects(r.celoRawReceipts[i], r.opRawReceipts[i])
+ if err != nil {
+ if r.celoRawReceipts[i]["effectiveGasPrice"] != nil && r.opRawReceipts[i]["effectiveGasPrice"] == nil {
+ fmt.Printf("dangling state at block %d\n", r.blockNumber-1)
+ } else {
+ return err
+ }
+ }
+ }
+ // Cross check receipts, here we decode the raw receipts into receipt objects and compare them, since the raw
+ // receipts are enriched with more fields than the receipt objects.
+ var celoRawConverted []*types.Receipt
+ jsonConvert(r.celoRawReceipts, &celoRawConverted)
+ err = EqualObjects(celoRawConverted, r.celoReceipts)
+ if err != nil {
+ spew.Dump("celorawreceipts", r.celoRawBlockReceipts)
+ return err
+ }
+
+ return nil
+}
+
+func (r *blockResults) verifyBlockReceipts() error {
+ // Check block receipts pairs
+ err := EqualObjects(r.celoBlockReceipts, r.opBlockReceipts)
+ if err != nil {
+ return err
+ }
+
+ // filter the raw op receipts
+ for i := range r.opRawBlockReceipts {
+ filterOpReceipt(r.opRawBlockReceipts[i])
+ }
+ err = EqualObjects(r.celoRawBlockReceipts, r.opRawBlockReceipts)
+ if err != nil {
+ return err
+ }
+
+ // Cross check receipts, here we decode the raw receipts into receipt objects and compare them, since the raw
+ // receipts are enriched with more fields than the receipt objects.
+ var celoRawConverted []*types.Receipt
+ jsonConvert(r.celoRawBlockReceipts, &celoRawConverted)
+
+ return EqualObjects(celoRawConverted, r.celoBlockReceipts)
+}
+
+func (r *blockResults) Verify(chainID uint64) error {
+ // Cross check the tx and receipt effective gas price calculation
+ for i, tx := range r.opRawTxs {
+ EqualObjects(tx["effectiveGasPrice"], r.opRawReceipts[i]["effectiveGasPrice"])
+ }
+
+ err := r.verifyBlocks(chainID)
+ if err != nil {
+ return err
+ }
+ err = r.verifyTransactions()
+ if err != nil {
+ return err
+ }
+ err = r.verifyReceipts()
+ if err != nil {
+ return err
+ }
+ err = r.verifyBlockReceipts()
+ if err != nil {
+ return err
+ }
+
+ // Check the block receipt, we only have raw values for this because there is no method to retrieve it via the ethclient.
+ // See https://docs.celo.org/developer/migrate/from-ethereum#core-contract-calls
+ err = EqualObjects(r.celoRawBlockReceipt, r.opRawBlockReceipt)
+ if err != nil {
+ return err
+ }
+
+ toCrosscheckWithBlockReceipts := r.celoRawReceipts
+
+ if r.celoRawBlockReceipt != nil {
+ // Strangely we always return a block receipt even if there are no logs,
+ // but we don't include it in the block receipts unless there are logs.
+ if len(r.celoRawBlockReceipt["logs"].([]interface{})) > 0 {
+ toCrosscheckWithBlockReceipts = append(toCrosscheckWithBlockReceipts, r.celoRawBlockReceipt)
+ }
+ }
+
+ // Cross check block receipts with receipts
+ err = EqualObjects(r.celoRawBlockReceipts, toCrosscheckWithBlockReceipts)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Retreives all the logs for this block in order.
+func (r *blockResults) Logs() ([]*types.Log, error) {
+ logs := make([]*types.Log, 0)
+ for _, receipt := range r.celoBlockReceipts {
+ logs = append(logs, receipt.Logs...)
+ }
+ return logs, nil
+}
+
+func makeBlockComparable(b *types.Block) {
+ // Blocks cache the hash
+ b.Hash()
+ makeTransactionsComparable(b.Transactions())
+}
+func makeTransactionsComparable(txs []*types.Transaction) {
+ // Transactions in blocks cache the hash and also have a locally set time which varies
+ for _, tx := range txs {
+ tx.SetTime(time.Time{})
+ tx.Hash()
+ }
+}
+
+func fetchBlockElements(ctx context.Context, clients *clients, blockNumber uint64, resultChan chan *blockResults) error {
+ ctx, cancel := context.WithTimeout(ctx, time.Minute)
+ defer cancel()
+ blockNum := big.NewInt(int64(blockNumber))
+ blockNumberHex := hexutil.EncodeUint64(blockNumber)
+
+ var err error
+ opBlockByNumber, err := clients.opEthclient.BlockByNumber(ctx, blockNum)
+ if err != nil {
+ return fmt.Errorf("%s: %w", "op.BlockByNumber", err)
+ }
+ blockHash := opBlockByNumber.Hash()
+ l := len(opBlockByNumber.Transactions())
+
+ results := &blockResults{
+ blockNumber: blockNumber,
+ opBlockByNumber: opBlockByNumber,
+ celoTxs: make([]*types.Transaction, l),
+ opTxs: make([]*types.Transaction, l),
+ celoRawTxs: make([]map[string]interface{}, l),
+ opRawTxs: make([]map[string]interface{}, l),
+ celoReceipts: make([]*types.Receipt, l),
+ opReceipts: make([]*types.Receipt, l),
+ celoRawReceipts: make([]map[string]interface{}, l),
+ opRawReceipts: make([]map[string]interface{}, l),
+ celoBlockReceipts: make([]*types.Receipt, l),
+ opBlockReceipts: make([]*types.Receipt, l),
+ }
+
+ g, ctx := errgroup.WithContext(ctx)
+ g.SetLimit(10)
+ g.Go(func() error {
+ var err error
+ results.opBlockByHash, err = clients.opEthclient.BlockByHash(ctx, opBlockByNumber.Hash())
+ if err != nil {
+ return fmt.Errorf("opEthclient.%s: %w", "op.BlockByHash", err)
+ }
+ return nil
+ })
+
+ getBlockByNumber := "eth_getBlockByNumber"
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockByNumber, getBlockByNumber, blockNumberHex, true)
+ if err != nil {
+ return fmt.Errorf("celoClient.%s: %w", getBlockByNumber, err)
+ }
+ return nil
+ })
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.opClient, &results.opRawBlockByNumber, getBlockByNumber, blockNumberHex, true)
+ if err != nil {
+ return fmt.Errorf("opClient.%s: %w", getBlockByNumber, err)
+ }
+ return nil
+ })
+
+ getBlockByHash := "eth_getBlockByHash"
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockByHash, getBlockByHash, blockHash.Hex(), true)
+ if err != nil {
+ return fmt.Errorf("celoClient.%s: %w", getBlockByHash, err)
+ }
+ return nil
+ })
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.opClient, &results.opRawBlockByHash, getBlockByHash, blockHash.Hex(), true)
+ if err != nil {
+ return fmt.Errorf("opClient.%s: %w", getBlockByHash, err)
+ }
+ return nil
+ })
+
+ getBlockReceipt := "eth_getBlockReceipt"
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockReceipt, getBlockReceipt, blockHash.Hex())
+ if err != nil {
+ return fmt.Errorf("celoClient.%s: %w", getBlockReceipt, err)
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.opClient, &results.opRawBlockReceipt, getBlockReceipt, blockHash.Hex())
+ if err != nil {
+ return fmt.Errorf("opClient.%s: %w", getBlockReceipt, err)
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ celoBlockReceipts, err := clients.celoEthclient.BlockReceipts(ctx, rpc.BlockNumberOrHashWithHash(blockHash, true))
+ if err != nil {
+ return fmt.Errorf("celoEthclient.BlockReceipts: %w", err)
+ }
+ results.celoBlockReceipts = celoBlockReceipts
+ return nil
+ })
+
+ g.Go(func() error {
+ opBlockReceipts, err := clients.opEthclient.BlockReceipts(ctx, rpc.BlockNumberOrHashWithHash(blockHash, true))
+ if err != nil {
+ return fmt.Errorf("opEthclient.BlockReceipts: %w", err)
+ }
+ results.opBlockReceipts = opBlockReceipts
+ return nil
+ })
+
+ getBlockReceipts := "eth_getBlockReceipts"
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockReceipts, getBlockReceipts, blockHash.Hex())
+ if err != nil {
+ return fmt.Errorf("celoClient.%s: %w", getBlockReceipts, err)
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.opClient, &results.opRawBlockReceipts, getBlockReceipts, blockHash.Hex())
+ if err != nil {
+ return fmt.Errorf("opClient.%s: %w", getBlockReceipts, err)
+ }
+ return nil
+ })
+
+ // For each transaction in blockByNumber we retrieve it using the
+ // celoEthclient, opEthclient, celoClient, and opClient. Each transaction is
+ // retrieved in its own goroutine and sets itself in the respective slice
+ // that is accessible from its closure.
+ for i, tx := range opBlockByNumber.Transactions() {
+ h := tx.Hash()
+ hexHash := h.Hex()
+ index := i
+ g.Go(func() error {
+ celoTx, _, err := clients.celoEthclient.TransactionByHash(ctx, h)
+ if err != nil {
+ return fmt.Errorf("celoEthclient.TransactionByHash: %s, err: %w", h, err)
+ }
+ results.celoTxs[index] = celoTx
+ return nil
+ })
+
+ g.Go(func() error {
+ opTx, _, err := clients.opEthclient.TransactionByHash(ctx, h)
+ if err != nil {
+ return fmt.Errorf("opEthclient.TransactionByHash: %w", err)
+ }
+ results.opTxs[index] = opTx
+ return nil
+ })
+
+ getTxByHash := "eth_getTransactionByHash"
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.celoClient, &results.celoRawTxs[index], getTxByHash, hexHash)
+ if err != nil {
+ return fmt.Errorf("celoClient.%s: %w", getTxByHash, err)
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.opClient, &results.opRawTxs[index], getTxByHash, hexHash)
+ if err != nil {
+ return fmt.Errorf("opClient.%s: %w", getTxByHash, err)
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ celoReceipt, err := clients.celoEthclient.TransactionReceipt(ctx, h)
+ if err != nil {
+ return fmt.Errorf("celoEthclient.TransactionReceipt: %w", err)
+ }
+ results.celoReceipts[index] = celoReceipt
+ return nil
+ })
+
+ g.Go(func() error {
+ opReceipt, err := clients.opEthclient.TransactionReceipt(ctx, h)
+ if err != nil {
+ return fmt.Errorf("opEthclient.TransactionReceipt: %w", err)
+ }
+ results.opReceipts[index] = opReceipt
+ return nil
+ })
+
+ getTransactionReceipt := "eth_getTransactionReceipt"
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.celoClient, &results.celoRawReceipts[index], getTransactionReceipt, hexHash)
+ if err != nil {
+ return fmt.Errorf("celoClient.%s: %w", getTransactionReceipt, err)
+ }
+ return nil
+ })
+
+ g.Go(func() error {
+ err := rpcCall(ctx, clients.opClient, &results.opRawReceipts[index], getTransactionReceipt, hexHash)
+ if err != nil {
+ return fmt.Errorf("opClient.%s: %w", getTransactionReceipt, err)
+ }
+ return nil
+ })
+ }
+
+ err = g.Wait()
+ if err != nil {
+ return err
+ }
+
+ resultChan <- results
+ return err
+}
+
+func jsonConvert(in, out any) error {
+ marshaled, err := json.Marshal(in)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(marshaled, out)
+}
+
+func rpcCall(ctx context.Context, cl *rpc.Client, result interface{}, method string, args ...interface{}) error {
+ err := cl.CallContext(ctx, result, method, args...)
+ if err != nil {
+ return err
+ }
+ if result == "null" {
+ return fmt.Errorf("response for %v %v should not be null", method, args)
+ }
+ return nil
+}
+
+func filterOpReceipt(receipt map[string]interface{}) {
+ // Delete effective gas price fields that are nil, on the celo side we do not add them.
+ v, ok := receipt["effectiveGasPrice"]
+ if ok && v == nil {
+ delete(receipt, "effectiveGasPrice")
+ }
+}
+
+func filterOpTx(tx map[string]interface{}) {
+ // Some txs on celo contain chainID all of them do on op, so we just remove it from both sides.
+ delete(tx, "chainId")
+ // Celo never returned yParity
+ delete(tx, "yParity")
+ // Since we unequivocally delete gatewayFee on the celo side we need to delete it here as well.
+ delete(tx, "gatewayFee")
+}
+
+func filterCeloTx(tx map[string]interface{}) {
+ // Some txs on celo contain chainID all of them do on op, so we just remove it from both sides.
+ delete(tx, "chainId")
+ // On the op side we now don't return ethCompatible when it's true, so we
+ // remove it from the celo response in this case.
+ txType := tx["type"].(string)
+ if txType == "0x0" && tx["ethCompatible"].(bool) {
+ delete(tx, "ethCompatible")
+ }
+ //It seems gateway fee is always added to all rpc transaction responses on celo because tx.GatewayFee returns 0 if
+ //it's not set,even ethcompatible ones, this is confusing so we have removed this in the op code, so we need to make
+ //sure the celo side matches.
+ delete(tx, "gatewayFee")
+}
+
+type filterQuery struct {
+ FromBlock hexutil.Uint64 `json:"fromBlock"`
+ ToBlock hexutil.Uint64 `json:"toBlock"`
+}
+
+var (
+ IstanbulExtraVanity = 32 // Fixed number of extra-data bytes reserved for validator vanity
+)
+
+// IstanbulAggregatedSeal is the aggregated seal for Istanbul blocks
+type IstanbulAggregatedSeal struct {
+ // Bitmap is a bitmap having an active bit for each validator that signed this block
+ Bitmap *big.Int
+ // Signature is an aggregated BLS signature resulting from signatures by each validator that signed this block
+ Signature []byte
+ // Round is the round in which the signature was created.
+ Round *big.Int
+}
+
+// IstanbulExtra is the extra-data for Istanbul blocks
+type IstanbulExtra struct {
+ // AddedValidators are the validators that have been added in the block
+ AddedValidators []common.Address
+ // AddedValidatorsPublicKeys are the BLS public keys for the validators added in the block
+ AddedValidatorsPublicKeys [][96]byte
+ // RemovedValidators is a bitmap having an active bit for each removed validator in the block
+ RemovedValidators *big.Int
+ // Seal is an ECDSA signature by the proposer
+ Seal []byte
+ // AggregatedSeal contains the aggregated BLS signature created via IBFT consensus.
+ AggregatedSeal IstanbulAggregatedSeal
+ // ParentAggregatedSeal contains and aggregated BLS signature for the previous block.
+ ParentAggregatedSeal IstanbulAggregatedSeal
+}
diff --git op-geth/compat_test/objdiff.go Celo/compat_test/objdiff.go
new file mode 100644
index 0000000000000000000000000000000000000000..d69d877ca737ec7ce1f9c273d5bdbdccafa448bd
--- /dev/null
+++ Celo/compat_test/objdiff.go
@@ -0,0 +1,393 @@
+//go:build compat_test
+
+// MIT License
+//
+// Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+package compat_tests
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "reflect"
+ "runtime/debug"
+ "time"
+
+ "github.com/davecgh/go-spew/spew"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/pmezard/go-difflib/difflib"
+ "github.com/stretchr/testify/assert"
+)
+
+// EqualBlocks compares two instances of types.Block and returns an error if they are not equal.
+func EqualBlocks(block1, block2 *types.Block) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("block %q mismatch: %w", block1.Hash(), err)
+ }
+ }()
+ if block1 == nil || block2 == nil {
+ if block1 == block2 {
+ return nil
+ }
+ return errors.New("one of the blocks is nil")
+ }
+
+ if block1.NumberU64() != block2.NumberU64() {
+ return fmt.Errorf("block numbers do not match: %d != %d", block1.NumberU64(), block2.NumberU64())
+ }
+
+ if block1.Hash() != block2.Hash() {
+ return fmt.Errorf("block hashes do not match: %s != %s", block1.Hash(), block2.Hash())
+ }
+
+ if block1.ParentHash() != block2.ParentHash() {
+ return fmt.Errorf("parent hashes do not match: %s != %s", block1.ParentHash(), block2.ParentHash())
+ }
+
+ if block1.UncleHash() != block2.UncleHash() {
+ return fmt.Errorf("uncle hashes do not match: %s != %s", block1.UncleHash(), block2.UncleHash())
+ }
+
+ if block1.Coinbase() != block2.Coinbase() {
+ return fmt.Errorf("coinbase addresses do not match: %s != %s", block1.Coinbase(), block2.Coinbase())
+ }
+
+ if block1.Root() != block2.Root() {
+ return fmt.Errorf("state roots do not match: %s != %s", block1.Root(), block2.Root())
+ }
+
+ if block1.TxHash() != block2.TxHash() {
+ return fmt.Errorf("transaction roots do not match: %s != %s", block1.TxHash(), block2.TxHash())
+ }
+
+ if block1.ReceiptHash() != block2.ReceiptHash() {
+ return fmt.Errorf("receipt roots do not match: %s != %s", block1.ReceiptHash(), block2.ReceiptHash())
+ }
+
+ if block1.Difficulty().Cmp(block2.Difficulty()) != 0 {
+ return fmt.Errorf("difficulties do not match: %s != %s", block1.Difficulty(), block2.Difficulty())
+ }
+
+ if block1.GasLimit() != block2.GasLimit() {
+ return fmt.Errorf("gas limits do not match: %d != %d", block1.GasLimit(), block2.GasLimit())
+ }
+
+ if block1.GasUsed() != block2.GasUsed() {
+ return fmt.Errorf("gas used do not match: %d != %d", block1.GasUsed(), block2.GasUsed())
+ }
+
+ if block1.Time() != block2.Time() {
+ return fmt.Errorf("timestamps do not match: %d != %d", block1.Time(), block2.Time())
+ }
+
+ if !bytes.Equal(block1.Extra(), block2.Extra()) {
+ return fmt.Errorf("extra data do not match: %s != %s", block1.Extra(), block2.Extra())
+ }
+
+ if block1.MixDigest() != block2.MixDigest() {
+ return fmt.Errorf("mix digests do not match: %s != %s", block1.MixDigest(), block2.MixDigest())
+ }
+
+ if block1.Nonce() != block2.Nonce() {
+ return fmt.Errorf("nonces do not match: %d != %d", block1.Nonce(), block2.Nonce())
+ }
+
+ if err := EqualTransactionSlices(block1.Transactions(), block2.Transactions()); err != nil {
+ return fmt.Errorf("transactions do not match: %w", err)
+ }
+
+ return nil
+}
+
+// EqualTransactionSlices compares two slices of types.Transaction and returns an error if they are not equal.
+func EqualTransactionSlices(txs1, txs2 []*types.Transaction) error {
+ if len(txs1) != len(txs2) {
+ return errors.New("transaction slices are of different lengths")
+ }
+
+ for i := range txs1 {
+ if err := EqualTransactions(txs1[i], txs2[i]); err != nil {
+ return fmt.Errorf("transaction at index %d mismatch: %w", i, err)
+ }
+ }
+
+ return nil
+}
+
+// EqualTransactions compares two instances of types.Transaction and returns an error if they are not equal.
+func EqualTransactions(tx1, tx2 *types.Transaction) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("transaction %q mismatch: %w", tx1.Hash(), err)
+ }
+ }()
+ if tx1 == nil || tx2 == nil {
+ if tx1 == tx2 {
+ return nil
+ }
+ return errors.New("one of the transactions is nil")
+ }
+
+ if tx1.Type() != tx2.Type() {
+ return fmt.Errorf("types do not match: %d != %d", tx1.Type(), tx2.Type())
+ }
+
+ if tx1.Nonce() != tx2.Nonce() {
+ return fmt.Errorf("nonces do not match: %d != %d", tx1.Nonce(), tx2.Nonce())
+ }
+
+ if tx1.GasPrice().Cmp(tx2.GasPrice()) != 0 {
+ return fmt.Errorf("gas prices do not match: %s != %s", tx1.GasPrice(), tx2.GasPrice())
+ }
+
+ if tx1.GasFeeCap().Cmp(tx2.GasFeeCap()) != 0 {
+ return fmt.Errorf("gas fee caps do not match: %s != %s", tx1.GasFeeCap(), tx2.GasFeeCap())
+ }
+
+ if tx1.GasTipCap().Cmp(tx2.GasTipCap()) != 0 {
+ return fmt.Errorf("gas tip caps do not match: %s != %s", tx1.GasTipCap(), tx2.GasTipCap())
+ }
+
+ if tx1.Gas() != tx2.Gas() {
+ return fmt.Errorf("gas limits do not match: %d != %d", tx1.Gas(), tx2.Gas())
+ }
+
+ if tx1.To() == nil && tx2.To() != nil || tx1.To() != nil && tx2.To() == nil {
+ return errors.New("one of the recipient addresses is nil")
+ }
+
+ if tx1.To() != nil && tx2.To() != nil && *tx1.To() != *tx2.To() {
+ return fmt.Errorf("recipient addresses do not match: %s != %s", tx1.To().Hex(), tx2.To().Hex())
+ }
+
+ if tx1.Value().Cmp(tx2.Value()) != 0 {
+ return fmt.Errorf("values do not match: %s != %s", tx1.Value(), tx2.Value())
+ }
+
+ if !reflect.DeepEqual(tx1.Data(), tx2.Data()) {
+ return errors.New("data payloads do not match")
+ }
+
+ if !reflect.DeepEqual(tx1.AccessList(), tx2.AccessList()) {
+ return errors.New("access lists do not match")
+ }
+
+ if tx1.ChainId().Cmp(tx2.ChainId()) != 0 {
+ return fmt.Errorf("chain IDs do not match: %s != %s", tx1.ChainId(), tx2.ChainId())
+ }
+
+ if tx1.Hash() != tx2.Hash() {
+ return fmt.Errorf("hashes do not match: %s != %s", tx1.Hash().Hex(), tx2.Hash().Hex())
+ }
+
+ if tx1.Size() != tx2.Size() {
+ return fmt.Errorf("sizes do not match: %d != %d", tx1.Size(), tx2.Size())
+ }
+
+ if tx1.Protected() != tx2.Protected() {
+ return fmt.Errorf("protected flags do not match: %t != %t", tx1.Protected(), tx2.Protected())
+ }
+ r1, s1, v1 := tx1.RawSignatureValues()
+ r2, s2, v2 := tx2.RawSignatureValues()
+ if r1.Cmp(r2) != 0 || s1.Cmp(s2) != 0 || v1.Cmp(v2) != 0 {
+ return errors.New("transaction signature values do not match")
+ }
+
+ return nil
+}
+
+func EqualObjects(expected, actual interface{}, msgAndArgs ...interface{}) error {
+ msg := messageFromMsgAndArgs(msgAndArgs...)
+ if err := validateEqualArgs(expected, actual); err != nil {
+ return fmt.Errorf("%s: Invalid operation: %#v == %#v (%s)", msg, expected, actual, err)
+ }
+ // A workaround for the atomic pointers now used to store the block and transaction hashes.
+ b1, ok1 := expected.(*types.Block)
+ b2, ok2 := actual.(*types.Block)
+ if ok1 && ok2 {
+ return EqualBlocks(b1, b2)
+ }
+ t1, ok1 := expected.(*types.Transaction)
+ t2, ok2 := actual.(*types.Transaction)
+ if ok1 && ok2 {
+ return EqualTransactions(t1, t2)
+ }
+ ts1, ok1 := expected.([]*types.Transaction)
+ ts2, ok2 := actual.([]*types.Transaction)
+ if ok1 && ok2 {
+ return EqualTransactionSlices(ts1, ts2)
+ }
+
+ if !assert.ObjectsAreEqual(expected, actual) {
+ diff := diff(expected, actual)
+
+ expected, actual = formatUnequalValues(expected, actual)
+ return fmt.Errorf("%s: Not equal: \n"+
+ "expected: %s\n"+
+ "actual : %s%s\n"+
+ "stack: : %s\n", msg, expected, actual, diff, string(debug.Stack()))
+ }
+
+ return nil
+}
+
+// validateEqualArgs checks whether provided arguments can be safely used in the
+// Equal/NotEqual functions.
+func validateEqualArgs(expected, actual interface{}) error {
+ if expected == nil && actual == nil {
+ return nil
+ }
+
+ if isFunction(expected) || isFunction(actual) {
+ return errors.New("cannot take func type as argument")
+ }
+ return nil
+}
+
+func isFunction(arg interface{}) bool {
+ if arg == nil {
+ return false
+ }
+ return reflect.TypeOf(arg).Kind() == reflect.Func
+}
+
+// diff returns a diff of both values as long as both are of the same type and
+// are a struct, map, slice, array or string. Otherwise it returns an empty string.
+func diff(expected interface{}, actual interface{}) string {
+ if expected == nil || actual == nil {
+ return ""
+ }
+
+ et, ek := typeAndKind(expected)
+ at, _ := typeAndKind(actual)
+
+ if et != at {
+ return ""
+ }
+
+ if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array && ek != reflect.String {
+ return ""
+ }
+
+ var e, a string
+
+ switch et {
+ case reflect.TypeOf(""):
+ e = reflect.ValueOf(expected).String()
+ a = reflect.ValueOf(actual).String()
+ case reflect.TypeOf(time.Time{}):
+ e = spewConfigStringerEnabled.Sdump(expected)
+ a = spewConfigStringerEnabled.Sdump(actual)
+ default:
+ e = spewConfig.Sdump(expected)
+ a = spewConfig.Sdump(actual)
+ }
+
+ diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
+ A: difflib.SplitLines(e),
+ B: difflib.SplitLines(a),
+ FromFile: "Expected",
+ FromDate: "",
+ ToFile: "Actual",
+ ToDate: "",
+ Context: 1,
+ })
+
+ return "\n\nDiff:\n" + diff
+}
+
+var spewConfig = spew.ConfigState{
+ Indent: " ",
+ DisablePointerAddresses: true,
+ DisableCapacities: true,
+ SortKeys: true,
+ DisableMethods: true,
+ MaxDepth: 10,
+}
+
+var spewConfigStringerEnabled = spew.ConfigState{
+ Indent: " ",
+ DisablePointerAddresses: true,
+ DisableCapacities: true,
+ SortKeys: true,
+ MaxDepth: 10,
+}
+
+func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
+ t := reflect.TypeOf(v)
+ k := t.Kind()
+
+ if k == reflect.Ptr {
+ t = t.Elem()
+ k = t.Kind()
+ }
+ return t, k
+}
+
+// formatUnequalValues takes two values of arbitrary types and returns string
+// representations appropriate to be presented to the user.
+//
+// If the values are not of like type, the returned strings will be prefixed
+// with the type name, and the value will be enclosed in parenthesis similar
+// to a type conversion in the Go grammar.
+func formatUnequalValues(expected, actual interface{}) (e string, a string) {
+ if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
+ return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)),
+ fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual))
+ }
+ switch expected.(type) {
+ case time.Duration:
+ return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual)
+ }
+ return truncatingFormat(expected), truncatingFormat(actual)
+}
+
+// truncatingFormat formats the data and truncates it if it's too long.
+//
+// This helps keep formatted error messages lines from exceeding the
+// bufio.MaxScanTokenSize max line length that the go testing framework imposes.
+func truncatingFormat(data interface{}) string {
+ value := fmt.Sprintf("%#v", data)
+ max := bufio.MaxScanTokenSize - 100 // Give us some space the type info too if needed.
+ if len(value) > max {
+ value = value[0:max] + "<... truncated>"
+ }
+ return value
+}
+
+func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
+ if len(msgAndArgs) == 0 || msgAndArgs == nil {
+ return ""
+ }
+ if len(msgAndArgs) == 1 {
+ msg := msgAndArgs[0]
+ if msgAsStr, ok := msg.(string); ok {
+ return msgAsStr
+ }
+ return fmt.Sprintf("%+v", msg)
+ }
+ if len(msgAndArgs) > 1 {
+ return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
+ }
+ return ""
+}
diff --git op-geth/compat_test/smoke_compat_test.go Celo/compat_test/smoke_compat_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6dbd617f089296d910f7c3a04f1f3ec060428e11
--- /dev/null
+++ Celo/compat_test/smoke_compat_test.go
@@ -0,0 +1,206 @@
+//go:build compat_test
+
+package compat_tests
+
+import (
+ "context"
+ "flag"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/sync/errgroup"
+)
+
+var (
+ // CLI arguments
+ blockInterval uint64
+ enableRandomBlockTest bool
+
+ clientOpts = []rpc.ClientOption{
+ rpc.WithWebsocketMessageSizeLimit(1024 * 1024 * 256),
+ }
+)
+
+func init() {
+ flag.Uint64Var(&blockInterval, "block-interval", 1_000_000, "number of blocks to skip between test iterations")
+ flag.BoolVar(&enableRandomBlockTest, "random-block", false, "enable random block height within the range during test iterations")
+}
+
+func TestSmokeRPCCompatibilities(t *testing.T) {
+ // TestSmokeRPCCompatibilities checks the compatibility of Celo L1 and Celo L2 RPC responses.
+ // The test retrieves the following types of data at the beginning, the last L1 block, and at every one million blocks to verify compatibility.
+ // 1. Block
+ // 2. Transactions
+ // 3. Transaction Receipts
+ // 4. Block Receipts
+ flag.Parse()
+
+ if celoRpcURL == "" {
+ t.Fatal("celo rpc url not set example usage:\n go test -v ./compat_test -tags compat_test -run TestSmokeRPCCompatibilities -celo-url ws://localhost:8546 -op-geth-url ws://localhost:9994")
+ }
+ if opGethRpcURL == "" {
+ t.Fatal("op-geth rpc url not set example usage:\n go test -v ./compat_test -tags compat_test -run TestSmokeRPCCompatibilities -celo-url ws://localhost:8546 -op-geth-url ws://localhost:9994")
+ }
+ if blockInterval == 0 {
+ t.Fatal("block interval must be positive integer:\n go test -v ./compat_test -tags compat_test -run TestSmokeRPCCompatibilities -celo-url ws://localhost:8546 -op-geth-url ws://localhost:9994 -block-interval 1000000")
+ }
+
+ // Setup RPC clients for Celo L1 and Celo L2 server
+ celoClient, err := rpc.DialOptions(context.Background(), celoRpcURL, clientOpts...)
+ require.NoError(t, err)
+ celoEthClient := ethclient.NewClient(celoClient)
+
+ opClient, err := rpc.DialOptions(context.Background(), opGethRpcURL, clientOpts...)
+ require.NoError(t, err)
+ opEthClient := ethclient.NewClient(opClient)
+
+ initCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ t.Cleanup(cancel)
+
+ // Fetch Chain IDs and make sure they're same
+ celoChainId, err := celoEthClient.ChainID(initCtx)
+ require.NoError(t, err)
+ opChainId, err := opEthClient.ChainID(initCtx)
+ require.NoError(t, err)
+ require.Equal(t, celoChainId.Uint64(), opChainId.Uint64(), "chain ids of referenced chains differ")
+
+ // Make sure the Chain ID is supported
+ chainId := celoChainId.Uint64()
+ _, ok := gingerbreadBlocks[chainId]
+ require.True(t, ok, "chain id %d not found in supported chainIDs %v", chainId, gingerbreadBlocks)
+
+ // Fetch the last block height in Celo L1
+ lastCeloL1BlockHeight, err := celoEthClient.BlockNumber(initCtx)
+ require.NoError(t, err)
+
+ t.Logf("Last Celo L1 Block Height: %d", lastCeloL1BlockHeight)
+
+ resultChan := make(chan *blockResults, 100)
+ globalCtx, cancel := context.WithCancel(context.Background())
+ t.Cleanup(cancel)
+
+ // Fetching RPC data job
+ fetchingEg, jobCtx := errgroup.WithContext(globalCtx)
+ fetchingEg.SetLimit(6)
+
+ fetchingEg.Go(func() error {
+ clients := &clients{
+ celoEthclient: celoEthClient,
+ opEthclient: opEthClient,
+ celoClient: celoClient,
+ opClient: opClient,
+ }
+
+ // Fetch data of the first 2 blocks
+ asyncFetchRpcData(t, jobCtx, fetchingEg, 0, clients, resultChan)
+ asyncFetchRpcData(t, jobCtx, fetchingEg, 1, clients, resultChan)
+
+ // Fetch some random blocks between blockInterval to lastCeloL1BlockHeight
+ for currentBlockNumber := uint64(0); currentBlockNumber < lastCeloL1BlockHeight; currentBlockNumber += blockInterval {
+ // exit loop if context is canceled
+ if isContextCanceled(jobCtx) {
+ t.Logf("Context canceled, exiting fetching loop at height %d", currentBlockNumber)
+ return nil
+ }
+
+ // decide block number to fetch between [currentBlockNumber, min(currentBlockNumber+blockInterval-1, lastCeloL1BlockHeight))
+ offset := uint64(0)
+ if enableRandomBlockTest {
+ randomUpperBound := blockInterval
+ if currentBlockNumber+randomUpperBound-1 >= lastCeloL1BlockHeight {
+ randomUpperBound = lastCeloL1BlockHeight - currentBlockNumber
+ }
+
+ // Int63n returns a non-negative random number
+ offset = uint64(rand.Int63n(int64(randomUpperBound)))
+ }
+
+ targetHeight := currentBlockNumber + offset
+ if targetHeight < 2 {
+ // ignore block at 0 and 1 because they're already fetched
+ if blockInterval == 1 {
+ continue
+ }
+
+ targetHeight = 2
+ }
+
+ // Fetch data at the point of current range
+ asyncFetchRpcData(t, jobCtx, fetchingEg, targetHeight, clients, resultChan)
+ }
+
+ // Fetch data at the last height
+ asyncFetchRpcData(t, jobCtx, fetchingEg, lastCeloL1BlockHeight, clients, resultChan)
+
+ return nil
+ })
+
+ // Testing fetched data job
+ testingEg, jobCtx := errgroup.WithContext(globalCtx)
+ testingEg.Go(func() error {
+ for result := range resultChan {
+ if isContextCanceled(jobCtx) {
+ t.Logf("Context canceled, exiting testing loop at height %d", result.blockNumber)
+ return nil
+ }
+
+ result := result
+ testingEg.Go(func() error {
+ err := result.Verify(chainId)
+ if err != nil {
+ t.Errorf("data at height %d err: %v\n", result.blockNumber, err)
+
+ return err
+ }
+
+ t.Logf("Verified data at height %d", result.blockNumber)
+
+ return nil
+ })
+ }
+
+ return nil
+ })
+
+ // Wait for fetching and testing jobs to finish
+ fetchingEg.Wait()
+ close(resultChan) // close resultChan to signal testingEg to end
+ testingEg.Wait()
+}
+
+func isContextCanceled(ctx context.Context) bool {
+ select {
+ case <-ctx.Done():
+ return true
+ default:
+ return false
+ }
+}
+
+func asyncFetchRpcData(
+ t *testing.T,
+ ctx context.Context,
+ eg *errgroup.Group,
+ blockNumber uint64,
+ clients *clients,
+ resultChan chan *blockResults,
+) {
+ t.Helper()
+
+ eg.Go(func() error {
+ err := fetchBlockElements(ctx, clients, blockNumber, resultChan)
+ if err != nil {
+ t.Errorf("failed to fetch block elements at height %d: %v", blockNumber, err)
+
+ return err
+ }
+
+ t.Logf("Fetched data at height %d", blockNumber)
+
+ return nil
+ })
+}
diff --git op-geth/consensus/misc/eip1559/eip1559.go Celo/consensus/misc/eip1559/eip1559.go
index a970bdcd31b3bed1ff75e6e2647c16a3cee024ad..af8f59d9352642de17d7bb4d5c4ea927f6fe1d39 100644
--- op-geth/consensus/misc/eip1559/eip1559.go
+++ Celo/consensus/misc/eip1559/eip1559.go
@@ -135,9 +135,21 @@ }
// CalcBaseFee calculates the basefee of the header.
// The time belongs to the new block to check which upgrades are active.
-func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64) *big.Int {
+// **Notice** that the return value is catched by the deferred function which can change the return value
+func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64) (response *big.Int) {
+ defer func() {
+ // If the base fee response is below the floor, intercept the return and return the floor instead.
+ if config.Celo != nil {
+ response = math.BigMax(response, new(big.Int).SetUint64(config.Celo.EIP1559BaseFeeFloor))
+ }
+ }()
+
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
- if !config.IsLondon(parent.Number) {
+ // For cel2 the london hardfork is enabled at the transition block, but we want to smoothly continue
+ // using our existing base fee and simply transition the calculation logic across to the real eip1559 logic
+ // I.E. stop using original celo logic defined in a smart contract. So if gingerbread was active for the parent
+ // block we don't set the base fee to the InitialBaseFee, and instead use the normal calculation logic.
+ if !config.IsLondon(parent.Number) && !config.IsGingerbread(parent.Number) {
return new(big.Int).SetUint64(params.InitialBaseFee)
}
elasticity := config.ElasticityMultiplier()
diff --git op-geth/consensus/misc/eip1559/eip1559_test.go Celo/consensus/misc/eip1559/eip1559_test.go
index c69dc80f93ac9e040e6a10e420ceaffb5a892724..8dabf1f8a79013052a2504db1844484ac3e5268e 100644
--- op-geth/consensus/misc/eip1559/eip1559_test.go
+++ Celo/consensus/misc/eip1559/eip1559_test.go
@@ -71,6 +71,15 @@ }
return config
}
+func celoConfig() *params.ChainConfig {
+ config := copyConfig(params.TestChainConfig)
+ ct := uint64(10)
+ config.Cel2Time = &ct
+ config.Celo = ¶ms.CeloConfig{EIP1559BaseFeeFloor: params.InitialBaseFee}
+
+ return config
+}
+
// TestBlockGasLimits tests the gasLimit checks for blocks both across
// the EIP-1559 boundary and post-1559 blocks
func TestBlockGasLimits(t *testing.T) {
@@ -218,3 +227,30 @@ t.Errorf("test %d: have %d want %d, ", i, have, want)
}
}
}
+
+// TestCalcBaseFeeCeloFloor assumes all blocks are 1559-blocks
+func TestCalcBaseFeeCeloFloor(t *testing.T) {
+ config := celoConfig()
+ tests := []struct {
+ parentBaseFee int64
+ parentGasLimit uint64
+ parentGasUsed uint64
+ expectedBaseFee int64
+ }{
+ {params.InitialBaseFee, 20000000, 10000000, params.InitialBaseFee}, // usage == target
+ {params.InitialBaseFee, 20000000, 7000000, params.InitialBaseFee}, // usage below target
+ {params.InitialBaseFee, 20000000, 11000000, 1012500000}, // usage above target
+ }
+ for i, test := range tests {
+ parent := &types.Header{
+ Number: common.Big32,
+ GasLimit: test.parentGasLimit,
+ GasUsed: test.parentGasUsed,
+ BaseFee: big.NewInt(test.parentBaseFee),
+ Time: *config.Cel2Time,
+ }
+ if have, want := CalcBaseFee(config, parent, parent.Time+2), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 {
+ t.Errorf("test %d: have %d want %d, ", i, have, want)
+ }
+ }
+}
diff --git op-geth/contracts/addresses/addresses.go Celo/contracts/addresses/addresses.go
new file mode 100644
index 0000000000000000000000000000000000000000..1b893bd4873fe274f901e62bd89bf16d09674e19
--- /dev/null
+++ Celo/contracts/addresses/addresses.go
@@ -0,0 +1,63 @@
+package addresses
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+type CeloAddresses struct {
+ CeloToken common.Address
+ FeeHandler common.Address
+ FeeCurrencyDirectory common.Address
+}
+
+var (
+ MainnetAddresses = &CeloAddresses{
+ CeloToken: common.HexToAddress("0x471ece3750da237f93b8e339c536989b8978a438"),
+ FeeHandler: common.HexToAddress("0xcd437749e43a154c07f3553504c68fbfd56b8778"),
+ FeeCurrencyDirectory: common.HexToAddress("0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF"), // TODO
+ }
+
+ AlfajoresAddresses = &CeloAddresses{
+ CeloToken: common.HexToAddress("0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9"),
+ FeeHandler: common.HexToAddress("0xEAaFf71AB67B5d0eF34ba62Ea06Ac3d3E2dAAA38"),
+ FeeCurrencyDirectory: common.HexToAddress("0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF"),
+ }
+
+ BaklavaAddresses = &CeloAddresses{
+ CeloToken: common.HexToAddress("0xdDc9bE57f553fe75752D61606B94CBD7e0264eF8"),
+ FeeHandler: common.HexToAddress("0xeed0A69c51079114C280f7b936C79e24bD94013e"),
+ FeeCurrencyDirectory: common.HexToAddress("0xD59E1599F45e42Eb356202B2C714D6C7b734C034"),
+ }
+)
+
+// GetAddresses returns the addresses for the given chainID or
+// nil if not found.
+func GetAddresses(chainID *big.Int) *CeloAddresses {
+ // ChainID can be uninitialized in some tests
+ if chainID == nil {
+ return nil
+ }
+ switch chainID.Uint64() {
+ case params.CeloAlfajoresChainID:
+ return AlfajoresAddresses
+ case params.CeloBaklavaChainID:
+ return BaklavaAddresses
+ case params.CeloMainnetChainID:
+ return MainnetAddresses
+ default:
+ return nil
+ }
+}
+
+// GetAddressesOrDefault returns the addresses for the given chainID or
+// the Mainnet addresses if none are found.
+func GetAddressesOrDefault(chainID *big.Int, defaultValue *CeloAddresses) *CeloAddresses {
+ addresses := GetAddresses(chainID)
+ if addresses == nil {
+ return defaultValue
+ }
+ return addresses
+}
diff --git op-geth/contracts/celo_backend.go Celo/contracts/celo_backend.go
new file mode 100644
index 0000000000000000000000000000000000000000..86c8cf78eeed58d49273d3874bdc2a7b03086e3a
--- /dev/null
+++ Celo/contracts/celo_backend.go
@@ -0,0 +1,80 @@
+package contracts
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+// CeloBackend provide a partial ContractBackend implementation, so that we can
+// access core contracts during block processing.
+type CeloBackend struct {
+ ChainConfig *params.ChainConfig
+ State vm.StateDB
+}
+
+// ContractCaller implementation
+
+func (b *CeloBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
+ return b.State.GetCode(contract), nil
+}
+
+func (b *CeloBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
+ // Ensure message is initialized properly.
+ if call.Gas == 0 {
+ // Chosen to be the same as ethconfig.Defaults.RPCGasCap
+ call.Gas = 50000000
+ }
+ if call.Value == nil {
+ call.Value = new(big.Int)
+ }
+
+ // Minimal initialization, might need to be extended when CeloBackend
+ // is used in more places. Also initializing blockNumber and time with
+ // 0 works now, but will break once we add hardforks at a later time.
+ if blockNumber == nil {
+ blockNumber = common.Big0
+ }
+ blockCtx := vm.BlockContext{
+ BlockNumber: blockNumber,
+ Time: 0,
+ Random: &common.Hash{}, // Setting this is important since it is used to set IsMerge
+ }
+ txCtx := vm.TxContext{}
+ vmConfig := vm.Config{}
+
+ // While StaticCall does not actually change state, it changes the
+ // access lists. We don't want this to add any access list changes, so
+ // we do a snapshot+revert.
+ snapshot := b.State.Snapshot()
+ evm := vm.NewEVM(blockCtx, txCtx, b.State, b.ChainConfig, vmConfig)
+ ret, _, err := evm.StaticCall(vm.AccountRef(evm.Origin), *call.To, call.Data, call.Gas)
+ b.State.RevertToSnapshot(snapshot)
+
+ return ret, err
+}
+
+// Get a vm.EVM object of you can't use the abi wrapper via the ContractCaller interface
+// This is usually the case when executing functions that modify state.
+func (b *CeloBackend) NewEVM(feeCurrencyContext *common.FeeCurrencyContext) *vm.EVM {
+ blockCtx := vm.BlockContext{
+ BlockNumber: new(big.Int),
+ Time: 0,
+ Transfer: func(state vm.StateDB, from common.Address, to common.Address, value *uint256.Int) {
+ if value.Cmp(common.U2560) != 0 {
+ panic("Non-zero transfers not implemented, yet.")
+ }
+ },
+ }
+ if feeCurrencyContext != nil {
+ blockCtx.FeeCurrencyContext = *feeCurrencyContext
+ }
+ txCtx := vm.TxContext{}
+ vmConfig := vm.Config{}
+ return vm.NewEVM(blockCtx, txCtx, b.State, b.ChainConfig, vmConfig)
+}
diff --git op-geth/core/bench_test.go Celo/core/bench_test.go
index 639d36e9aea673745f01b630ccb0d3aed8fd3fe8..ca199bbe0cd5ac6a2e3bf49f74827d1acae4098b 100644
--- op-geth/core/bench_test.go
+++ Celo/core/bench_test.go
@@ -83,7 +83,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
- gas, _ := IntrinsicGas(data, nil, false, false, false, false)
+ gas, _ := IntrinsicGas(data, nil, false, false, false, false, nil, common.IntrinsicGasCosts{})
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
diff --git op-geth/core/blockchain.go Celo/core/blockchain.go
index bbf57c3ccb7a8ede9f5615b5da316c1226da1b1b..057da21738415840335d1a9fee6bc819a3208a3b 100644
--- op-geth/core/blockchain.go
+++ Celo/core/blockchain.go
@@ -83,6 +83,9 @@ blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
blockCrossValidationTimer = metrics.NewRegisteredResettingTimer("chain/crossvalidation", nil)
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
+ blockGasUsedGauge = metrics.NewRegisteredGauge("chain/gasused", nil)
+ blockBaseFeeGauge = metrics.NewRegisteredGauge("chain/basefee", nil)
+ blockTxIncluded = metrics.NewRegisteredGauge("chain/txincluded", nil)
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
@@ -1987,6 +1990,14 @@ triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start)
+
+ blockGasUsedGauge.Update(int64(res.GasUsed))
+ if block.Header().BaseFee != nil {
+ blockBaseFeeGauge.Update(block.Header().BaseFee.Int64())
+ }
+ if block.Transactions() != nil {
+ blockTxIncluded.Update(int64(block.Transactions().Len()))
+ }
return &blockProcessingResult{usedGas: res.GasUsed, procTime: proctime, status: status}, nil
}
diff --git op-geth/core/celo_multi_gaspool.go Celo/core/celo_multi_gaspool.go
new file mode 100644
index 0000000000000000000000000000000000000000..c5bd7beed1a0624a5344b357e2315ff4368ce221
--- /dev/null
+++ Celo/core/celo_multi_gaspool.go
@@ -0,0 +1,70 @@
+package core
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type FeeCurrency = common.Address
+
+// MultiGasPool tracks the amount of gas available during execution
+// of the transactions in a block per fee currency. The zero value is a pool
+// with zero gas available.
+type MultiGasPool struct {
+ pools map[FeeCurrency]*GasPool
+ defaultPool *GasPool
+}
+
+type FeeCurrencyLimitMapping = map[FeeCurrency]float64
+
+// NewMultiGasPool creates a multi-fee currency gas pool and a default fallback
+// pool for CELO
+func NewMultiGasPool(
+ blockGasLimit uint64,
+ allowlist common.AddressSet,
+ defaultLimit float64,
+ limitsMapping FeeCurrencyLimitMapping,
+) *MultiGasPool {
+ pools := make(map[FeeCurrency]*GasPool, len(allowlist))
+
+ for currency := range allowlist {
+ fraction, ok := limitsMapping[currency]
+ if !ok {
+ fraction = defaultLimit
+ }
+
+ pools[currency] = new(GasPool).AddGas(
+ uint64(float64(blockGasLimit) * fraction),
+ )
+ }
+
+ // A special case for CELO which doesn't have a limit
+ celoPool := new(GasPool).AddGas(blockGasLimit)
+
+ return &MultiGasPool{
+ pools: pools,
+ defaultPool: celoPool,
+ }
+}
+
+// PoolFor returns a configured pool for the given fee currency or the default
+// one otherwise
+func (mgp MultiGasPool) PoolFor(feeCurrency *FeeCurrency) *GasPool {
+ if feeCurrency == nil || mgp.pools[*feeCurrency] == nil {
+ return mgp.defaultPool
+ }
+
+ return mgp.pools[*feeCurrency]
+}
+
+func (mgp MultiGasPool) Copy() *MultiGasPool {
+ pools := make(map[FeeCurrency]*GasPool, len(mgp.pools))
+ for fc, gp := range mgp.pools {
+ gpCpy := *gp
+ pools[fc] = &gpCpy
+ }
+ gpCpy := *mgp.defaultPool
+ return &MultiGasPool{
+ pools: pools,
+ defaultPool: &gpCpy,
+ }
+}
diff --git op-geth/core/celo_multi_gaspool_test.go Celo/core/celo_multi_gaspool_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8c658ad4ae0a3312e1397afeae862e9444d86b5a
--- /dev/null
+++ Celo/core/celo_multi_gaspool_test.go
@@ -0,0 +1,127 @@
+package core
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+func TestMultiCurrencyGasPool(t *testing.T) {
+ blockGasLimit := uint64(1_000)
+ subGasAmount := 100
+
+ cUSDToken := common.HexToAddress("0x765DE816845861e75A25fCA122bb6898B8B1282a")
+ cEURToken := common.HexToAddress("0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73")
+
+ testCases := []struct {
+ name string
+ feeCurrency *FeeCurrency
+ allowlist common.AddressSet
+ defaultLimit float64
+ limits FeeCurrencyLimitMapping
+ defaultPoolExpected bool
+ expectedValue uint64
+ }{
+ {
+ name: "Empty allowlist, empty mapping, CELO uses default pool",
+ feeCurrency: nil,
+ allowlist: common.NewAddressSet(),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{},
+ defaultPoolExpected: true,
+ expectedValue: 900, // blockGasLimit - subGasAmount
+ },
+ {
+ name: "Non-empty allowlist, non-empty mapping, CELO uses default pool",
+ feeCurrency: nil,
+ allowlist: common.NewAddressSet(cUSDToken),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{
+ cUSDToken: 0.5,
+ },
+ defaultPoolExpected: true,
+ expectedValue: 900, // blockGasLimit - subGasAmount
+ },
+ {
+ name: "Empty allowlist, empty mapping, non-registered currency fallbacks to the default pool",
+ feeCurrency: &cUSDToken,
+ allowlist: common.NewAddressSet(),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{},
+ defaultPoolExpected: true,
+ expectedValue: 900, // blockGasLimit - subGasAmount
+ },
+ {
+ name: "Non-empty allowlist, non-empty mapping, non-registered currency uses default pool",
+ feeCurrency: &cEURToken,
+ allowlist: common.NewAddressSet(cUSDToken),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{
+ cUSDToken: 0.5,
+ },
+ defaultPoolExpected: true,
+ expectedValue: 900, // blockGasLimit - subGasAmount
+ },
+ {
+ name: "Non-empty allowlist, empty mapping, registered currency uses default limit",
+ feeCurrency: &cUSDToken,
+ allowlist: common.NewAddressSet(cUSDToken),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{},
+ defaultPoolExpected: false,
+ expectedValue: 800, // blockGasLimit * defaultLimit - subGasAmount
+ },
+ {
+ name: "Non-empty allowlist, non-empty mapping, configured registered currency uses configured limits",
+ feeCurrency: &cUSDToken,
+ allowlist: common.NewAddressSet(cUSDToken),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{
+ cUSDToken: 0.5,
+ },
+ defaultPoolExpected: false,
+ expectedValue: 400, // blockGasLimit * 0.5 - subGasAmount
+ },
+ {
+ name: "Non-empty allowlist, non-empty mapping, unconfigured registered currency uses default limit",
+ feeCurrency: &cEURToken,
+ allowlist: common.NewAddressSet(cUSDToken, cEURToken),
+ defaultLimit: 0.9,
+ limits: map[FeeCurrency]float64{
+ cUSDToken: 0.5,
+ },
+ defaultPoolExpected: false,
+ expectedValue: 800, // blockGasLimit * 0.5 - subGasAmount
+ },
+ }
+
+ for _, c := range testCases {
+ t.Run(c.name, func(t *testing.T) {
+ mgp := NewMultiGasPool(
+ blockGasLimit,
+ c.allowlist,
+ c.defaultLimit,
+ c.limits,
+ )
+
+ pool := mgp.PoolFor(c.feeCurrency)
+ pool.SubGas(uint64(subGasAmount))
+
+ if c.defaultPoolExpected {
+ result := mgp.PoolFor(nil).Gas()
+ if result != c.expectedValue {
+ t.Error("Default pool expected", c.expectedValue, "got", result)
+ }
+ } else {
+ result := mgp.PoolFor(c.feeCurrency).Gas()
+
+ if result != c.expectedValue {
+ t.Error(
+ "Expected pool", c.feeCurrency, "value", c.expectedValue,
+ "got", result,
+ )
+ }
+ }
+ })
+ }
+}
diff --git op-geth/core/celo_state_transition.go Celo/core/celo_state_transition.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8d7df632c63f05ae2903f4cafae7bd19f74157d
--- /dev/null
+++ Celo/core/celo_state_transition.go
@@ -0,0 +1,186 @@
+package core
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/contracts/addresses"
+ "github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+// canPayFee checks whether accountOwner's balance can cover transaction fee.
+func (st *StateTransition) canPayFee(checkAmountForGas *big.Int) error {
+ var checkAmountInCelo, checkAmountInAlternativeCurrency *big.Int
+ if st.msg.FeeCurrency == nil {
+ checkAmountInCelo = new(big.Int).Add(checkAmountForGas, st.msg.Value)
+ checkAmountInAlternativeCurrency = common.Big0
+ } else {
+ checkAmountInCelo = st.msg.Value
+ checkAmountInAlternativeCurrency = checkAmountForGas
+ }
+
+ if checkAmountInCelo.Cmp(common.Big0) > 0 {
+ balanceInCeloU256, overflow := uint256.FromBig(checkAmountInCelo)
+ if overflow {
+ return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
+ }
+
+ balance := st.state.GetBalance(st.msg.From)
+
+ if balance.Cmp(balanceInCeloU256) < 0 {
+ return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), balance, checkAmountInCelo)
+ }
+ }
+ if checkAmountInAlternativeCurrency.Cmp(common.Big0) > 0 {
+ _, overflow := uint256.FromBig(checkAmountInAlternativeCurrency)
+ if overflow {
+ return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
+ }
+ backend := &contracts.CeloBackend{
+ ChainConfig: st.evm.ChainConfig(),
+ State: st.state,
+ }
+ balance, err := contracts.GetBalanceERC20(backend, st.msg.From, *st.msg.FeeCurrency)
+ if err != nil {
+ return err
+ }
+
+ if balance.Cmp(checkAmountInAlternativeCurrency) < 0 {
+ return fmt.Errorf("%w: address %v have %v want %v, fee currency: %v", ErrInsufficientFunds, st.msg.From.Hex(), balance, checkAmountInAlternativeCurrency, st.msg.FeeCurrency.Hex())
+ }
+ }
+ return nil
+}
+
+func (st *StateTransition) subFees(effectiveFee *big.Int) (err error) {
+ log.Trace("Debiting fee", "from", st.msg.From, "amount", effectiveFee, "feeCurrency", st.msg.FeeCurrency)
+
+ // native currency
+ if st.msg.FeeCurrency == nil {
+ effectiveFeeU256, _ := uint256.FromBig(effectiveFee)
+ st.state.SubBalance(st.msg.From, effectiveFeeU256, tracing.BalanceDecreaseGasBuy)
+ return nil
+ } else {
+ gasUsedDebit, err := contracts.DebitFees(st.evm, st.msg.FeeCurrency, st.msg.From, effectiveFee)
+ st.feeCurrencyGasUsed += gasUsedDebit
+ return err
+ }
+}
+
+// distributeTxFees calculates the amounts and recipients of transaction fees and credits the accounts.
+func (st *StateTransition) distributeTxFees() error {
+ if st.evm.Config.NoBaseFee && st.msg.GasFeeCap.Sign() == 0 && st.msg.GasTipCap.Sign() == 0 {
+ // Skip fee payment when NoBaseFee is set and the fee fields
+ // are 0. This avoids a negative effectiveTip being applied to
+ // the coinbase when simulating calls.
+ return nil
+ }
+
+ // Determine the refund and transaction fee to be distributed.
+ refund := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
+ gasUsed := new(big.Int).SetUint64(st.gasUsed())
+ totalTxFee := new(big.Int).Mul(gasUsed, st.msg.GasPrice)
+ from := st.msg.From
+
+ // Divide the transaction into a base (the minimum transaction fee) and tip (any extra, or min(max tip, feecap - GPM) if espresso).
+ baseTxFee := new(big.Int).Mul(gasUsed, st.calculateBaseFee())
+ // No need to do effectiveTip calculation, because st.gasPrice == effectiveGasPrice, and effectiveTip = effectiveGasPrice - baseTxFee
+ tipTxFee := new(big.Int).Sub(totalTxFee, baseTxFee)
+
+ feeCurrency := st.msg.FeeCurrency
+ feeHandlerAddress := addresses.GetAddressesOrDefault(st.evm.ChainConfig().ChainID, addresses.MainnetAddresses).FeeHandler
+
+ log.Trace("distributeTxFees", "from", from, "refund", refund, "feeCurrency", feeCurrency,
+ "coinbaseFeeRecipient", st.evm.Context.Coinbase, "coinbaseFee", tipTxFee,
+ "feeHandler", feeHandlerAddress, "communityFundFee", baseTxFee)
+
+ rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
+ var l1Cost *big.Int
+ // Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules)
+ // Note optimismConfig will not be nil if rules.IsOptimismBedrock is true
+ if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil &&
+ rules.IsOptimismBedrock && !st.msg.IsDepositTx {
+ l1Cost = st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time)
+ }
+
+ if feeCurrency == nil {
+ tipTxFeeU256, overflow := uint256.FromBig(tipTxFee)
+ if overflow {
+ return fmt.Errorf("celo tip overflows U256: %d", tipTxFee)
+ }
+ st.state.AddBalance(st.evm.Context.Coinbase, tipTxFeeU256, tracing.BalanceIncreaseRewardTransactionFee)
+
+ refundU256, overflow := uint256.FromBig(refund)
+ if overflow {
+ return fmt.Errorf("celo refund overflows U256: %d", refund)
+ }
+ st.state.AddBalance(from, refundU256, tracing.BalanceIncreaseGasReturn)
+
+ baseTxFeeU256, overflow := uint256.FromBig(baseTxFee)
+ if overflow {
+ return fmt.Errorf("celo base fee overflows U256: %d", baseTxFee)
+ }
+ if rules.IsCel2 {
+ st.state.AddBalance(feeHandlerAddress, baseTxFeeU256, tracing.BalanceIncreaseRewardTransactionFee)
+ } else if st.evm.ChainConfig().Optimism != nil {
+ st.state.AddBalance(params.OptimismBaseFeeRecipient, baseTxFeeU256, tracing.BalanceIncreaseRewardTransactionFee)
+ }
+
+ l1CostU256, overflow := uint256.FromBig(l1Cost)
+ if overflow {
+ return fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost)
+ }
+ if l1Cost != nil {
+ st.state.AddBalance(params.OptimismL1FeeRecipient, l1CostU256, tracing.BalanceIncreaseRewardTransactionFee)
+ }
+ } else {
+ if l1Cost != nil {
+ l1Cost, _ = exchange.ConvertCeloToCurrency(st.evm.Context.FeeCurrencyContext.ExchangeRates, feeCurrency, l1Cost)
+ }
+ if err := contracts.CreditFees(
+ st.evm,
+ feeCurrency,
+ from,
+ st.evm.Context.Coinbase,
+ feeHandlerAddress,
+ params.OptimismL1FeeRecipient,
+ refund,
+ tipTxFee,
+ baseTxFee,
+ l1Cost,
+ st.feeCurrencyGasUsed,
+ ); err != nil {
+ log.Error("Error crediting", "from", from, "coinbase", st.evm.Context.Coinbase, "feeHandler", feeHandlerAddress, "err", err)
+ return err
+ }
+ }
+
+ if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
+ st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
+ }
+ return nil
+}
+
+// calculateBaseFee returns the correct base fee to use during fee calculations
+// This is the base fee from the header if no fee currency is used, but the
+// base fee converted to fee currency when a fee currency is used.
+func (st *StateTransition) calculateBaseFee() *big.Int {
+ baseFee := st.evm.Context.BaseFee
+ if baseFee == nil {
+ // This can happen in pre EIP-1559 environments
+ baseFee = big.NewInt(0)
+ }
+
+ if st.msg.FeeCurrency != nil {
+ // Existence of the fee currency has been checked in `preCheck`
+ baseFee, _ = exchange.ConvertCeloToCurrency(st.evm.Context.FeeCurrencyContext.ExchangeRates, st.msg.FeeCurrency, baseFee)
+ }
+
+ return baseFee
+}
diff --git op-geth/core/chain_makers.go Celo/core/chain_makers.go
index bc5ac8417389553b75155f023a6fced32eee45d2..27a85519dc34ea5e6be5fcca6a922571fa9f8f12 100644
--- op-geth/core/chain_makers.go
+++ Celo/core/chain_makers.go
@@ -39,11 +39,12 @@
// BlockGen creates blocks for testing.
// See GenerateChain for a detailed explanation.
type BlockGen struct {
- i int
- cm *chainMaker
- parent *types.Block
- header *types.Header
- statedb *state.StateDB
+ i int
+ cm *chainMaker
+ parent *types.Block
+ header *types.Header
+ statedb *state.StateDB
+ feeCurrencyContext *common.FeeCurrencyContext
gasPool *GasPool
txs []*types.Transaction
@@ -52,6 +53,10 @@ uncles []*types.Header
withdrawals []*types.Withdrawal
engine consensus.Engine
+}
+
+func (b *BlockGen) updateFeeCurrencyContext() {
+ b.feeCurrencyContext = GetFeeCurrencyContext(b.header, b.cm.config, b.statedb)
}
// SetCoinbase sets the coinbase of the generated block.
@@ -99,7 +104,7 @@ // block.
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
b.header.ParentBeaconRoot = &root
var (
- blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, b.cm.config, b.statedb)
+ blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, b.cm.config, b.statedb, b.feeCurrencyContext)
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
)
ProcessBeaconBlockRoot(root, vmenv, b.statedb)
@@ -117,7 +122,7 @@ if b.gasPool == nil {
b.SetCoinbase(common.Address{})
}
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
- receipt, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig)
+ receipt, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig, b.feeCurrencyContext)
if err != nil {
panic(err)
}
@@ -329,6 +334,8 @@ // Post-merge chain
b.header.Difficulty = big.NewInt(0)
}
}
+ b.updateFeeCurrencyContext()
+
// Mutate the state and block according to any hard-fork specs
if daoBlock := config.DAOForkBlock; daoBlock != nil {
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
@@ -441,6 +448,7 @@
genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine}
b.header = cm.makeHeader(parent, statedb, b.engine)
+ b.updateFeeCurrencyContext()
// TODO uncomment when proof generation is merged
// Save pre state for proof generation
diff --git op-geth/core/error.go Celo/core/error.go
index b8b00121eba7c062f0909e34af4768972cb100b2..19b837838fdcaa37b82ab610680f8d64227f5bcc 100644
--- op-geth/core/error.go
+++ Celo/core/error.go
@@ -119,4 +119,7 @@ ErrBlobTxCreate = errors.New("blob transaction of type create")
// ErrSystemTxNotSupported is returned for any deposit tx with IsSystemTx=true after the Regolith fork
ErrSystemTxNotSupported = errors.New("system tx not supported")
+
+ // ErrCel2NotEnabled is returned if a feature requires the Cel2 fork, but that is not enabled.
+ ErrCel2NotEnabled = errors.New("required cel2 fork not enabled")
)
diff --git op-geth/core/gen_genesis.go Celo/core/gen_genesis.go
index 64840dec2d95080f15212107b7cb673ca1477489..f4aec12026c290a42e50059db4afda1c62cc85d0 100644
--- op-geth/core/gen_genesis.go
+++ Celo/core/gen_genesis.go
@@ -23,8 +23,8 @@ Config *params.ChainConfig `json:"config"`
Nonce math.HexOrDecimal64 `json:"nonce"`
Timestamp math.HexOrDecimal64 `json:"timestamp"`
ExtraData hexutil.Bytes `json:"extraData"`
- GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
- Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
+ GasLimit math.HexOrDecimal64 `json:"gasLimit"`
+ Difficulty *math.HexOrDecimal256 `json:"difficulty"`
Mixhash common.Hash `json:"mixHash"`
Coinbase common.Address `json:"coinbase"`
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
@@ -68,8 +68,8 @@ Config *params.ChainConfig `json:"config"`
Nonce *math.HexOrDecimal64 `json:"nonce"`
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
ExtraData *hexutil.Bytes `json:"extraData"`
- GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
- Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
+ GasLimit *math.HexOrDecimal64 `json:"gasLimit"`
+ Difficulty *math.HexOrDecimal256 `json:"difficulty"`
Mixhash *common.Hash `json:"mixHash"`
Coinbase *common.Address `json:"coinbase"`
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
@@ -97,14 +97,12 @@ }
if dec.ExtraData != nil {
g.ExtraData = *dec.ExtraData
}
- if dec.GasLimit == nil {
- return errors.New("missing required field 'gasLimit' for Genesis")
+ if dec.GasLimit != nil {
+ g.GasLimit = uint64(*dec.GasLimit)
}
- g.GasLimit = uint64(*dec.GasLimit)
- if dec.Difficulty == nil {
- return errors.New("missing required field 'difficulty' for Genesis")
+ if dec.Difficulty != nil {
+ g.Difficulty = (*big.Int)(dec.Difficulty)
}
- g.Difficulty = (*big.Int)(dec.Difficulty)
if dec.Mixhash != nil {
g.Mixhash = *dec.Mixhash
}
diff --git op-geth/core/rawdb/accessors_chain.go Celo/core/rawdb/accessors_chain.go
index 4914d961ec60e48ba3194f8f2a2eecfd8909c55d..b6fe2540c4adc6369681ebcc40b9dcadeb61695b 100644
--- op-geth/core/rawdb/accessors_chain.go
+++ Celo/core/rawdb/accessors_chain.go
@@ -694,8 +694,24 @@ }
// DecodeRLP implements rlp.Decoder.
func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
+ // Retrieve the entire receipt blob as we need to try multiple decoders
+ blob, err := s.Raw()
+ if err != nil {
+ return err
+ }
+ // Check to see if this is a celo dynamic fee receipt.
+ if types.IsCeloDynamicFeeReceipt(blob) {
+ var stored types.CeloDynamicFeeStoredReceiptRLP
+ err := rlp.DecodeBytes(blob, &stored)
+ if err != nil {
+ return err
+ }
+ r.Logs = stored.Logs
+ return nil
+ }
+
var stored storedReceiptRLP
- if err := s.Decode(&stored); err != nil {
+ if err := rlp.DecodeBytes(blob, &stored); err != nil {
return err
}
r.Logs = stored.Logs
diff --git op-geth/core/state_prefetcher.go Celo/core/state_prefetcher.go
index 3a8fab85e3c9a01ff3df89b2f51f670d89d148db..52f988c44a3af93624cc1c93df980b9652021782 100644
--- op-geth/core/state_prefetcher.go
+++ Celo/core/state_prefetcher.go
@@ -46,11 +46,12 @@ // the transaction messages using the statedb, but any changes are discarded. The
// only goal is to pre-cache transaction signatures and state trie nodes.
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) {
var (
- header = block.Header()
- gaspool = new(GasPool).AddGas(block.GasLimit())
- blockContext = NewEVMBlockContext(header, p.chain, nil, p.config, statedb)
- evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
- signer = types.MakeSigner(p.config, header.Number, header.Time)
+ header = block.Header()
+ gaspool = new(GasPool).AddGas(block.GasLimit())
+ feeCurrencyContext = GetFeeCurrencyContext(header, p.config, statedb)
+ blockContext = NewEVMBlockContext(header, p.chain, nil, p.config, statedb, feeCurrencyContext)
+ evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
+ signer = types.MakeSigner(p.config, header.Number, header.Time)
)
// Iterate over and process the individual transactions
byzantium := p.config.IsByzantium(block.Number())
@@ -60,7 +61,7 @@ if interrupt != nil && interrupt.Load() {
return
}
// Convert the transaction into an executable message and pre-cache its sender
- msg, err := TransactionToMessage(tx, signer, header.BaseFee)
+ msg, err := TransactionToMessage(tx, signer, header.BaseFee, blockContext.FeeCurrencyContext.ExchangeRates)
if err != nil {
return // Also invalid block, bail out
}
diff --git op-geth/core/state_processor_test.go Celo/core/state_processor_test.go
index 2ee74f02ea237e52168889537dcd123159111de1..8357a81d8d6a2d9e859a1a20ddbdc13e9670de1f 100644
--- op-geth/core/state_processor_test.go
+++ Celo/core/state_processor_test.go
@@ -132,8 +132,8 @@ Nonce: math.MaxUint64,
},
},
}
- blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
- tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
+ blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
+ //tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
)
defer blockchain.Stop()
@@ -238,12 +238,13 @@ mkDynamicTx(0, common.Address{}, params.TxGas, bigNumber, bigNumber),
},
want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 required balance exceeds 256 bits",
},
- { // ErrMaxInitCodeSizeExceeded
- txs: []*types.Transaction{
- mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
- },
- want: "could not apply tx 0 [0xd491405f06c92d118dd3208376fcee18a57c54bc52063ee4a26b1cf296857c25]: max initcode size exceeded: code size 49153 limit 49152",
- },
+ // Disabled due to Celo MaxCodeSize change
+ // { // ErrMaxInitCodeSizeExceeded
+ // txs: []*types.Transaction{
+ // mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
+ // },
+ // want: "could not apply tx 0 [0xd491405f06c92d118dd3208376fcee18a57c54bc52063ee4a26b1cf296857c25]: max initcode size exceeded: code size 49153 limit 49152",
+ // },
{ // ErrIntrinsicGas: Not enough gas to cover init code
txs: []*types.Transaction{
mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.InitialBaseFee), make([]byte, 320)),
@@ -430,12 +431,12 @@ }
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
- intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true)
+ intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true, nil, nil)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
- intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true)
+ intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true, nil, nil)
)
func TestProcessVerkle(t *testing.T) {
@@ -584,11 +585,13 @@ statedb.SetNonce(params.HistoryStorageAddress, 1)
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode)
statedb.IntermediateRoot(true)
- vmContext := NewEVMBlockContext(header, nil, &coinbase, chainConfig, statedb)
+ feeCurrencyContext := GetFeeCurrencyContext(header, chainConfig, statedb)
+
+ vmContext := NewEVMBlockContext(header, nil, &coinbase, chainConfig, statedb, feeCurrencyContext)
evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{})
ProcessParentBlockHash(header.ParentHash, evm, statedb)
- vmContext = NewEVMBlockContext(parent, nil, &coinbase, chainConfig, statedb)
+ vmContext = NewEVMBlockContext(parent, nil, &coinbase, chainConfig, statedb, feeCurrencyContext)
evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{})
ProcessParentBlockHash(parent.ParentHash, evm, statedb)
diff --git op-geth/core/tracing/gen_balance_change_reason_stringer.go Celo/core/tracing/gen_balance_change_reason_stringer.go
index d3a515a12d37bb2c106d4808984f83a72840c75b..891f586715ffb06aaacfaf796dd2d168cb3a49fd 100644
--- op-geth/core/tracing/gen_balance_change_reason_stringer.go
+++ Celo/core/tracing/gen_balance_change_reason_stringer.go
@@ -23,15 +23,25 @@ _ = x[BalanceChangeTouchAccount-11]
_ = x[BalanceIncreaseSelfdestruct-12]
_ = x[BalanceDecreaseSelfdestruct-13]
_ = x[BalanceDecreaseSelfdestructBurn-14]
+ _ = x[BalanceMint-200]
}
-const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn"
+const (
+ _BalanceChangeReason_name_0 = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn"
+ _BalanceChangeReason_name_1 = "BalanceMint"
+)
-var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400}
+var (
+ _BalanceChangeReason_index_0 = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400}
+)
func (i BalanceChangeReason) String() string {
- if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
+ switch {
+ case i <= 14:
+ return _BalanceChangeReason_name_0[_BalanceChangeReason_index_0[i]:_BalanceChangeReason_index_0[i+1]]
+ case i == 200:
+ return _BalanceChangeReason_name_1
+ default:
return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
}
- return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]]
}
diff --git op-geth/core/tracing/hooks.go Celo/core/tracing/hooks.go
index 5397bcc7543f24d2b0a2f2a82afec2f0a4e731fb..b7d3687e64522a204a3b5e095f058e830540def0 100644
--- op-geth/core/tracing/hooks.go
+++ Celo/core/tracing/hooks.go
@@ -194,6 +194,9 @@ OnNonceChange NonceChangeHook
OnCodeChange CodeChangeHook
OnStorageChange StorageChangeHook
OnLog LogHook
+
+ // Celo specific: should the tracer be run when fee currencies are debited/credited for gas?
+ TraceDebitCredit bool
}
// BalanceChangeReason is used to indicate the reason for a balance change, useful
diff --git op-geth/core/types/block.go Celo/core/types/block.go
index 621891e4b4850c0be9e66c152fd6ab8c9e876b78..5817e5cab4819a26fe1ee3a1ab6859f6b5802257 100644
--- op-geth/core/types/block.go
+++ Celo/core/types/block.go
@@ -68,7 +68,6 @@ VerkleProof *verkle.VerkleProof `json:"verkleProof"`
}
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
-//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
// Header represents a block header in the Ethereum blockchain.
type Header struct {
@@ -105,6 +104,9 @@ ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
+
+ // preGingerbread determines whether this is a pre-gingerbread header, which determines how this block will be encoded.
+ preGingerbread bool
}
// field type overrides for gencodec
diff --git op-geth/core/types/celo_block.go Celo/core/types/celo_block.go
new file mode 100644
index 0000000000000000000000000000000000000000..3c69ee6f02b4c4e6eb4b3f649fbdeb5cd90502c3
--- /dev/null
+++ Celo/core/types/celo_block.go
@@ -0,0 +1,114 @@
+package types
+
+import (
+ "io"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+//go:generate go run ../../rlp/rlpgen -type BeforeGingerbreadHeader --encoder --decoder -out gen_before_gingerbread_header_rlp.go
+//go:generate go run ../../rlp/rlpgen -type AfterGingerbreadHeader --encoder --decoder -out gen_after_gingerbread_header_rlp.go
+
+type IstanbulExtra rlp.RawValue
+
+type BeforeGingerbreadHeader struct {
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ Coinbase common.Address `json:"miner" gencodec:"required"`
+ Root common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
+ ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
+ Bloom Bloom `json:"logsBloom" gencodec:"required"`
+ Number *big.Int `json:"number" gencodec:"required"`
+ GasUsed uint64 `json:"gasUsed" gencodec:"required"`
+ Time uint64 `json:"timestamp" gencodec:"required"`
+ Extra []byte `json:"extraData" gencodec:"required"`
+}
+
+// This type is required to avoid an infinite loop when decoding
+type AfterGingerbreadHeader Header
+
+func (h *Header) DecodeRLP(s *rlp.Stream) error {
+ var raw rlp.RawValue
+ err := s.Decode(&raw)
+ if err != nil {
+ return err
+ }
+
+ preGingerbread, err := isPreGingerbreadHeader(raw)
+ if err != nil {
+ return err
+ }
+
+ if preGingerbread { // Address
+ // Before gingerbread
+ decodedHeader := BeforeGingerbreadHeader{}
+ err = rlp.DecodeBytes(raw, &decodedHeader)
+
+ h.ParentHash = decodedHeader.ParentHash
+ h.Coinbase = decodedHeader.Coinbase
+ h.Root = decodedHeader.Root
+ h.TxHash = decodedHeader.TxHash
+ h.ReceiptHash = decodedHeader.ReceiptHash
+ h.Bloom = decodedHeader.Bloom
+ h.Number = decodedHeader.Number
+ h.GasUsed = decodedHeader.GasUsed
+ h.Time = decodedHeader.Time
+ h.Extra = decodedHeader.Extra
+ h.Difficulty = new(big.Int)
+ h.preGingerbread = true
+ } else {
+ // After gingerbread
+ decodedHeader := AfterGingerbreadHeader{}
+ err = rlp.DecodeBytes(raw, &decodedHeader)
+ *h = Header(decodedHeader)
+ }
+
+ return err
+}
+
+// EncodeRLP implements encodes the Header to an RLP data stream.
+func (h *Header) EncodeRLP(w io.Writer) error {
+ if h.IsPreGingerbread() {
+ encodedHeader := BeforeGingerbreadHeader{
+ ParentHash: h.ParentHash,
+ Coinbase: h.Coinbase,
+ Root: h.Root,
+ TxHash: h.TxHash,
+ ReceiptHash: h.ReceiptHash,
+ Bloom: h.Bloom,
+ Number: h.Number,
+ GasUsed: h.GasUsed,
+ Time: h.Time,
+ Extra: h.Extra,
+ }
+
+ return rlp.Encode(w, &encodedHeader)
+ }
+
+ // After gingerbread
+ encodedHeader := AfterGingerbreadHeader(*h)
+ return rlp.Encode(w, &encodedHeader)
+}
+
+// isPreGingerbreadHeader introspects the header rlp to check the length of the
+// second element of the list (the first element describes the list). Pre
+// gingerbread the second element of a header is an address which is 20 bytes
+// long, post gingerbread the second element is a hash which is 32 bytes long.
+func isPreGingerbreadHeader(buf []byte) (bool, error) {
+ var contentSize uint64
+ var err error
+ for i := 0; i < 3; i++ {
+ buf, _, _, contentSize, err = rlp.ReadNext(buf)
+ if err != nil {
+ return false, err
+ }
+ }
+
+ return contentSize == common.AddressLength, nil
+}
+
+func (h *Header) IsPreGingerbread() bool {
+ return h.preGingerbread
+}
diff --git op-geth/core/types/celo_block_test.go Celo/core/types/celo_block_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..eab7c7adaa70ce637524487b4d5806d11a7d55a0
--- /dev/null
+++ Celo/core/types/celo_block_test.go
@@ -0,0 +1,176 @@
+package types
+
+import (
+ "bytes"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/stretchr/testify/assert"
+)
+
+// mockOldBeforeGingerbreadHeader is same as BeforeGingerbreadHeader
+// but doesn't implement EncodeRLP and DecodeRLP
+type mockOldBeforeGingerbreadHeader BeforeGingerbreadHeader
+
+// mockOldAfterGingerbreadHeader is also same as AfterGingerbreadHeader
+type mockOldAfterGingerbreadHeader Header
+
+var (
+ mockBeforeGingerbreadHeader = &BeforeGingerbreadHeader{
+ ParentHash: common.HexToHash("0x112233445566778899001122334455667788990011223344556677889900aabb"),
+ Coinbase: common.HexToAddress("0x8888f1f195afa192cfee860698584c030f4c9db1"),
+ Root: EmptyRootHash,
+ TxHash: EmptyTxsHash,
+ ReceiptHash: EmptyReceiptsHash,
+ Bloom: Bloom(common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
+ Number: math.BigPow(2, 9),
+ GasUsed: 1476322,
+ Time: 9876543,
+ Extra: []byte("test before gingerbread header extra"),
+ }
+
+ mockWithdrawalHash = common.HexToHash("0x4585754a71d14791295bc094dc53eb0b32f21d92e58350a4140163a047b854a7")
+ mockExcessBlobGas = uint64(123456789)
+ mockBlobGasUsed = uint64(12345678)
+ mockParentBeaconRoot = common.HexToHash("0x9229c626ebd6328b3ddc7fe8636f2fd9a344f4c02e2e281f59a3b7e4e46833e5")
+ mockAfterGingerbreadHeader = &AfterGingerbreadHeader{
+ ParentHash: mockBeforeGingerbreadHeader.ParentHash,
+ UncleHash: EmptyUncleHash,
+ Coinbase: mockBeforeGingerbreadHeader.Coinbase,
+ Root: EmptyRootHash,
+ TxHash: EmptyTxsHash,
+ ReceiptHash: EmptyReceiptsHash,
+ Bloom: mockBeforeGingerbreadHeader.Bloom,
+ Difficulty: big.NewInt(17179869184),
+ Number: mockBeforeGingerbreadHeader.Number,
+ GasLimit: 12345678,
+ GasUsed: mockBeforeGingerbreadHeader.GasUsed,
+ Time: mockBeforeGingerbreadHeader.Time,
+ Extra: []byte("test after gingerbread header extra"),
+ MixDigest: common.HexToHash("0x036a0a7a3611ecd974ef274e603ceab81246fb50dc350519b9f47589e8fe3014"),
+ Nonce: EncodeNonce(12345),
+ BaseFee: math.BigPow(10, 8),
+ WithdrawalsHash: &mockWithdrawalHash,
+ ExcessBlobGas: &mockExcessBlobGas,
+ BlobGasUsed: &mockBlobGasUsed,
+ ParentBeaconRoot: &mockParentBeaconRoot,
+ }
+)
+
+func ToMockOldBeforeGingerbreadHeader(h *BeforeGingerbreadHeader) *mockOldBeforeGingerbreadHeader {
+ return &mockOldBeforeGingerbreadHeader{
+ ParentHash: h.ParentHash,
+ Coinbase: h.Coinbase,
+ Root: h.Root,
+ TxHash: h.TxHash,
+ ReceiptHash: h.ReceiptHash,
+ Bloom: h.Bloom,
+ Number: h.Number,
+ GasUsed: h.GasUsed,
+ Time: h.Time,
+ Extra: h.Extra,
+ }
+}
+
+func BeforeGingerbreadHeaderToHeader(h *BeforeGingerbreadHeader) *Header {
+ return &Header{
+ ParentHash: h.ParentHash,
+ Coinbase: h.Coinbase,
+ Root: h.Root,
+ TxHash: h.TxHash,
+ ReceiptHash: h.ReceiptHash,
+ Bloom: h.Bloom,
+ Number: h.Number,
+ GasUsed: h.GasUsed,
+ Time: h.Time,
+ Extra: h.Extra,
+ Difficulty: new(big.Int),
+ preGingerbread: true,
+ }
+}
+
+func TestRLPDecodeHeaderCompatibility(t *testing.T) {
+ tests := []struct {
+ name string
+ oldHeader interface{}
+ newHeader *Header
+ }{
+ {
+ name: "BeforeGingerbreadHeader",
+ oldHeader: ToMockOldBeforeGingerbreadHeader(mockBeforeGingerbreadHeader),
+ newHeader: BeforeGingerbreadHeaderToHeader(mockBeforeGingerbreadHeader),
+ },
+ {
+ name: "AfterGingerbreadHeader",
+ oldHeader: (*mockOldAfterGingerbreadHeader)(mockAfterGingerbreadHeader),
+ newHeader: (*Header)(mockAfterGingerbreadHeader),
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+
+ r := bytes.NewBuffer([]byte{})
+
+ // encode by reflection style
+ err := rlp.Encode(r, test.oldHeader)
+ assert.NoError(t, err, "failed RLP encode")
+
+ // decode by generated code
+ decodedHeader := &Header{}
+ rlp.DecodeBytes(r.Bytes(), decodedHeader)
+ assert.NoError(t, err, "failed RLP decode")
+
+ assert.Equal(t, test.newHeader, decodedHeader)
+ })
+ }
+}
+
+func TestRlpEncodeHeaderCompatibility(t *testing.T) {
+ tests := []struct {
+ name string
+ oldHeader interface{} // header type which doesn't implement EncodeRLP
+ newHeader *Header // header type which implements EncodeRLP
+ }{
+ {
+ name: "BeforeGingerbreadHeader",
+ oldHeader: ToMockOldBeforeGingerbreadHeader(mockBeforeGingerbreadHeader),
+ newHeader: BeforeGingerbreadHeaderToHeader(mockBeforeGingerbreadHeader),
+ },
+ {
+ name: "AfterGingerbreadHeader",
+ oldHeader: (*mockOldAfterGingerbreadHeader)(mockAfterGingerbreadHeader),
+ newHeader: (*Header)(mockAfterGingerbreadHeader),
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+
+ r := bytes.NewBuffer([]byte{})
+
+ // old RLP encoding
+ err := rlp.Encode(r, test.oldHeader)
+ assert.NoError(t, err, "failed RLP encode by reflection style")
+ oldEncodedData := r.Bytes()
+
+ r.Reset()
+
+ // new RLP encoding
+ err = rlp.Encode(r, test.newHeader)
+ assert.NoError(t, err, "failed RLP encode by generated code")
+ newEncodedData := r.Bytes()
+
+ assert.Equal(t, oldEncodedData, newEncodedData)
+ })
+ }
+}
diff --git op-geth/core/types/celo_denominated_tx.go Celo/core/types/celo_denominated_tx.go
new file mode 100644
index 0000000000000000000000000000000000000000..21ed9da5564d7ab1cebd75ca5ef1e5f94617d7dd
--- /dev/null
+++ Celo/core/types/celo_denominated_tx.go
@@ -0,0 +1,119 @@
+package types
+
+import (
+ "bytes"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+const CeloDenominatedTxType = 0x7a
+
+type CeloDenominatedTx struct {
+ ChainID *big.Int
+ Nonce uint64
+ GasTipCap *big.Int
+ GasFeeCap *big.Int
+ Gas uint64
+ To *common.Address `rlp:"nil"` // nil means contract creation
+ Value *big.Int
+ Data []byte
+ AccessList AccessList
+
+ FeeCurrency *common.Address
+ MaxFeeInFeeCurrency *big.Int
+
+ // Signature values
+ V *big.Int `json:"v" gencodec:"required"`
+ R *big.Int `json:"r" gencodec:"required"`
+ S *big.Int `json:"s" gencodec:"required"`
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *CeloDenominatedTx) copy() TxData {
+ cpy := &CeloDenominatedTx{
+ Nonce: tx.Nonce,
+ To: copyAddressPtr(tx.To),
+ Data: common.CopyBytes(tx.Data),
+ Gas: tx.Gas,
+ FeeCurrency: copyAddressPtr(tx.FeeCurrency),
+ // These are copied below.
+ MaxFeeInFeeCurrency: new(big.Int),
+ AccessList: make(AccessList, len(tx.AccessList)),
+ Value: new(big.Int),
+ ChainID: new(big.Int),
+ GasTipCap: new(big.Int),
+ GasFeeCap: new(big.Int),
+ V: new(big.Int),
+ R: new(big.Int),
+ S: new(big.Int),
+ }
+ if tx.MaxFeeInFeeCurrency != nil {
+ cpy.MaxFeeInFeeCurrency.Set(tx.MaxFeeInFeeCurrency)
+ }
+ copy(cpy.AccessList, tx.AccessList)
+ if tx.Value != nil {
+ cpy.Value.Set(tx.Value)
+ }
+ if tx.ChainID != nil {
+ cpy.ChainID.Set(tx.ChainID)
+ }
+ if tx.GasTipCap != nil {
+ cpy.GasTipCap.Set(tx.GasTipCap)
+ }
+ if tx.GasFeeCap != nil {
+ cpy.GasFeeCap.Set(tx.GasFeeCap)
+ }
+ if tx.V != nil {
+ cpy.V.Set(tx.V)
+ }
+ if tx.R != nil {
+ cpy.R.Set(tx.R)
+ }
+ if tx.S != nil {
+ cpy.S.Set(tx.S)
+ }
+ return cpy
+}
+
+// accessors for innerTx.
+func (tx *CeloDenominatedTx) txType() byte { return CeloDenominatedTxType }
+func (tx *CeloDenominatedTx) chainID() *big.Int { return tx.ChainID }
+func (tx *CeloDenominatedTx) accessList() AccessList { return tx.AccessList }
+func (tx *CeloDenominatedTx) data() []byte { return tx.Data }
+func (tx *CeloDenominatedTx) gas() uint64 { return tx.Gas }
+func (tx *CeloDenominatedTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
+func (tx *CeloDenominatedTx) gasTipCap() *big.Int { return tx.GasTipCap }
+func (tx *CeloDenominatedTx) gasPrice() *big.Int { return tx.GasFeeCap }
+func (tx *CeloDenominatedTx) value() *big.Int { return tx.Value }
+func (tx *CeloDenominatedTx) nonce() uint64 { return tx.Nonce }
+func (tx *CeloDenominatedTx) to() *common.Address { return tx.To }
+func (tx *CeloDenominatedTx) isSystemTx() bool { return false }
+
+func (tx *CeloDenominatedTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
+ if baseFee == nil {
+ return dst.Set(tx.GasFeeCap)
+ }
+ tip := dst.Sub(tx.GasFeeCap, baseFee)
+ if tip.Cmp(tx.GasTipCap) > 0 {
+ tip.Set(tx.GasTipCap)
+ }
+ return tip.Add(tip, baseFee)
+}
+
+func (tx *CeloDenominatedTx) rawSignatureValues() (v, r, s *big.Int) {
+ return tx.V, tx.R, tx.S
+}
+
+func (tx *CeloDenominatedTx) setSignatureValues(chainID, v, r, s *big.Int) {
+ tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
+}
+
+func (tx *CeloDenominatedTx) encode(b *bytes.Buffer) error {
+ return rlp.Encode(b, tx)
+}
+
+func (tx *CeloDenominatedTx) decode(input []byte) error {
+ return rlp.DecodeBytes(input, tx)
+}
diff --git op-geth/core/types/celo_dynamic_fee_tx_v2.go Celo/core/types/celo_dynamic_fee_tx_v2.go
new file mode 100644
index 0000000000000000000000000000000000000000..45f83a5b70b97ce68f22c77400d81d74ba083703
--- /dev/null
+++ Celo/core/types/celo_dynamic_fee_tx_v2.go
@@ -0,0 +1,131 @@
+// Copyright 2024 The Celo Authors
+// This file is part of the celo library.
+//
+// The celo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The celo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the celo library. If not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "bytes"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+const CeloDynamicFeeTxV2Type = 0x7b
+
+// CeloDynamicFeeTxV2 represents a CIP-64 transaction.
+type CeloDynamicFeeTxV2 struct {
+ ChainID *big.Int
+ Nonce uint64
+ GasTipCap *big.Int
+ GasFeeCap *big.Int
+ Gas uint64
+ To *common.Address `rlp:"nil"` // nil means contract creation
+ Value *big.Int
+ Data []byte
+ AccessList AccessList
+
+ FeeCurrency *common.Address `rlp:"nil"` // nil means native currency
+
+ // Signature values
+ V *big.Int `json:"v" gencodec:"required"`
+ R *big.Int `json:"r" gencodec:"required"`
+ S *big.Int `json:"s" gencodec:"required"`
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *CeloDynamicFeeTxV2) copy() TxData {
+ cpy := &CeloDynamicFeeTxV2{
+ Nonce: tx.Nonce,
+ To: copyAddressPtr(tx.To),
+ Data: common.CopyBytes(tx.Data),
+ Gas: tx.Gas,
+ FeeCurrency: copyAddressPtr(tx.FeeCurrency),
+ // These are copied below.
+ AccessList: make(AccessList, len(tx.AccessList)),
+ Value: new(big.Int),
+ ChainID: new(big.Int),
+ GasTipCap: new(big.Int),
+ GasFeeCap: new(big.Int),
+ V: new(big.Int),
+ R: new(big.Int),
+ S: new(big.Int),
+ }
+ copy(cpy.AccessList, tx.AccessList)
+ if tx.Value != nil {
+ cpy.Value.Set(tx.Value)
+ }
+ if tx.ChainID != nil {
+ cpy.ChainID.Set(tx.ChainID)
+ }
+ if tx.GasTipCap != nil {
+ cpy.GasTipCap.Set(tx.GasTipCap)
+ }
+ if tx.GasFeeCap != nil {
+ cpy.GasFeeCap.Set(tx.GasFeeCap)
+ }
+ if tx.V != nil {
+ cpy.V.Set(tx.V)
+ }
+ if tx.R != nil {
+ cpy.R.Set(tx.R)
+ }
+ if tx.S != nil {
+ cpy.S.Set(tx.S)
+ }
+ return cpy
+}
+
+// accessors for innerTx.
+func (tx *CeloDynamicFeeTxV2) txType() byte { return CeloDynamicFeeTxV2Type }
+func (tx *CeloDynamicFeeTxV2) chainID() *big.Int { return tx.ChainID }
+func (tx *CeloDynamicFeeTxV2) accessList() AccessList { return tx.AccessList }
+func (tx *CeloDynamicFeeTxV2) data() []byte { return tx.Data }
+func (tx *CeloDynamicFeeTxV2) gas() uint64 { return tx.Gas }
+func (tx *CeloDynamicFeeTxV2) gasFeeCap() *big.Int { return tx.GasFeeCap }
+func (tx *CeloDynamicFeeTxV2) gasTipCap() *big.Int { return tx.GasTipCap }
+func (tx *CeloDynamicFeeTxV2) gasPrice() *big.Int { return tx.GasFeeCap }
+func (tx *CeloDynamicFeeTxV2) value() *big.Int { return tx.Value }
+func (tx *CeloDynamicFeeTxV2) nonce() uint64 { return tx.Nonce }
+func (tx *CeloDynamicFeeTxV2) to() *common.Address { return tx.To }
+func (tx *CeloDynamicFeeTxV2) isSystemTx() bool { return false }
+
+func (tx *CeloDynamicFeeTxV2) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
+ if baseFee == nil {
+ return dst.Set(tx.GasFeeCap)
+ }
+ tip := dst.Sub(tx.GasFeeCap, baseFee)
+ if tip.Cmp(tx.GasTipCap) > 0 {
+ tip.Set(tx.GasTipCap)
+ }
+ return tip.Add(tip, baseFee)
+}
+
+func (tx *CeloDynamicFeeTxV2) rawSignatureValues() (v, r, s *big.Int) {
+ return tx.V, tx.R, tx.S
+}
+
+func (tx *CeloDynamicFeeTxV2) setSignatureValues(chainID, v, r, s *big.Int) {
+ tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
+}
+
+func (tx *CeloDynamicFeeTxV2) encode(b *bytes.Buffer) error {
+ return rlp.Encode(b, tx)
+}
+
+func (tx *CeloDynamicFeeTxV2) decode(input []byte) error {
+ return rlp.DecodeBytes(input, tx)
+}
diff --git op-geth/core/types/celo_receipt.go Celo/core/types/celo_receipt.go
new file mode 100644
index 0000000000000000000000000000000000000000..82a3862ca5742c733673a13609b0f85be4bc18c5
--- /dev/null
+++ Celo/core/types/celo_receipt.go
@@ -0,0 +1,51 @@
+package types
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+type celoDynamicReceiptRLP struct {
+ PostStateOrStatus []byte
+ CumulativeGasUsed uint64
+ Bloom Bloom
+ Logs []*Log
+ // BaseFee was introduced as mandatory in Cel2 ONLY for the CeloDynamicFeeTxs
+ BaseFee *big.Int `rlp:"optional"`
+}
+
+type CeloDynamicFeeStoredReceiptRLP struct {
+ CeloDynamicReceiptMarker []interface{} // Marker to distinguish this from storedReceiptRLP
+ PostStateOrStatus []byte
+ CumulativeGasUsed uint64
+ Logs []*Log
+ BaseFee *big.Int `rlp:"optional"`
+}
+
+// Detect CeloDynamicFee receipts by looking at the first list element
+// To distinguish these receipts from the very similar normal receipts, an
+// empty list is added as the first element of the RLP-serialized struct.
+func IsCeloDynamicFeeReceipt(blob []byte) bool {
+ listHeaderSize := 1 // Length of the list header representing the struct in bytes
+ if blob[0] > 0xf7 {
+ listHeaderSize += int(blob[0]) - 0xf7
+ }
+ firstListElement := blob[listHeaderSize] // First byte of first list element
+ return firstListElement == 0xc0
+}
+
+func decodeStoredCeloDynamicFeeReceiptRLP(r *ReceiptForStorage, blob []byte) error {
+ var stored CeloDynamicFeeStoredReceiptRLP
+ if err := rlp.DecodeBytes(blob, &stored); err != nil {
+ return err
+ }
+ if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
+ return err
+ }
+ r.CumulativeGasUsed = stored.CumulativeGasUsed
+ r.Logs = stored.Logs
+ r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
+ r.BaseFee = stored.BaseFee
+ return nil
+}
diff --git op-geth/core/types/celo_receipt_test.go Celo/core/types/celo_receipt_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b48ceab9811f0f77f1be89de6bda1cf8918d94d
--- /dev/null
+++ Celo/core/types/celo_receipt_test.go
@@ -0,0 +1,229 @@
+package types
+
+import (
+ "bytes"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/uint256"
+ "github.com/stretchr/testify/require"
+)
+
+func TestCeloDynamicFeeTxReceiptEncodeDecode(t *testing.T) {
+ checkEncodeDecodeConsistency(createTypedReceipt(CeloDynamicFeeTxType), t)
+}
+
+func TestCeloDynamicFeeTxV2ReceiptEncodeDecode(t *testing.T) {
+ t.Run("NoBaseFee", func(t *testing.T) {
+ checkEncodeDecodeConsistency(createTypedReceipt(CeloDynamicFeeTxV2Type), t)
+ })
+
+ t.Run("WithBaseFee", func(t *testing.T) {
+ r := createTypedReceipt(CeloDynamicFeeTxV2Type)
+ r.BaseFee = big.NewInt(1000)
+ checkEncodeDecodeConsistency(r, t)
+ })
+}
+
+func createTypedReceipt(receiptType uint8) *Receipt {
+ // Note this receipt and logs lack lots of fields, those fields are derived from the
+ // block and transaction and so are not part of encoding/decoding.
+ r := &Receipt{
+ Type: receiptType,
+ PostState: common.Hash{3}.Bytes(),
+ CumulativeGasUsed: 6,
+ Logs: []*Log{
+ {
+ Address: common.BytesToAddress([]byte{0x33}),
+ Topics: []common.Hash{common.HexToHash("dead")},
+ Data: []byte{0x01, 0x02, 0x03},
+ },
+ {
+ Address: common.BytesToAddress([]byte{0x03, 0x33}),
+ Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
+ Data: []byte{0x01, 0x02},
+ },
+ },
+ }
+ r.Bloom = CreateBloom(Receipts{r})
+ return r
+}
+
+// checkEncodeDecodeConsistency checks both RLP and binary encoding/decoding consistency.
+func checkEncodeDecodeConsistency(r *Receipt, t *testing.T) {
+ checkRLPEncodeDecodeConsistency(r, t)
+ checkStorageRLPEncodeDecodeConsistency((*ReceiptForStorage)(r), t)
+ checkBinaryEncodeDecodeConsistency(r, t)
+}
+
+// checkRLPEncodeDecodeConsistency encodes and decodes the receipt and checks that they are equal.
+func checkRLPEncodeDecodeConsistency(r *Receipt, t *testing.T) {
+ buf := new(bytes.Buffer)
+ err := rlp.Encode(buf, r)
+ require.NoError(t, err)
+
+ var r2 Receipt
+ err = rlp.Decode(buf, &r2)
+ require.NoError(t, err)
+
+ require.EqualValues(t, r, &r2)
+}
+
+// checkRLPEncodeDecodeConsistency encodes and decodes the receipt and checks that they are equal.
+func checkBinaryEncodeDecodeConsistency(r *Receipt, t *testing.T) {
+ bytes, err := r.MarshalBinary()
+ require.NoError(t, err)
+
+ r2 := &Receipt{}
+ err = r2.UnmarshalBinary(bytes)
+ require.NoError(t, err)
+
+ require.EqualValues(t, r, r2)
+}
+
+// checkStorageRLPEncodeDecodeConsistency encodes and decodes the receipt and checks that they are equal.
+func checkStorageRLPEncodeDecodeConsistency(r *ReceiptForStorage, t *testing.T) {
+ buf := new(bytes.Buffer)
+ err := rlp.Encode(buf, r)
+ require.NoError(t, err)
+
+ // Stored receipts do not encode the type, (although they do require it to be set during encoding)
+ // since it is derived from the associated transaction. So for the sake of the comparison we set it
+ // to 0 and restore it after the comparison.
+ receiptType := r.Type
+ defer func() { r.Type = receiptType }()
+ r.Type = 0
+
+ var r2 ReceiptForStorage
+ err = rlp.Decode(buf, &r2)
+ require.NoError(t, err)
+
+ require.EqualValues(t, r, &r2)
+}
+
+// Tests that the effective gas price is correctly derived for different transaction types, in different scenarios.
+func TestReceiptEffectiveGasPriceDerivation(t *testing.T) {
+ gasPrice := big.NewInt(1000)
+ gasFeeCap := big.NewInt(800)
+ gasTipCap := big.NewInt(100)
+ // Receipt base fee is the base fee encoded in the receipt which will be set post cel2 for CeloDynamicFeeTxV2 types.
+ receiptBaseFee := big.NewInt(50)
+
+ t.Run("LegacyTx", func(t *testing.T) {
+ testNonDynamic(t, NewTransaction(0, common.Address{}, big.NewInt(0), 0, gasPrice, nil), gasPrice)
+ })
+ t.Run("AccessListTx", func(t *testing.T) {
+ testNonDynamic(t, NewTx(&AccessListTx{GasPrice: gasPrice}), gasPrice)
+ })
+ t.Run("DynamicFeeTx", func(t *testing.T) {
+ tx := NewTx(&DynamicFeeTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap})
+ testDynamic(t, tx, nil)
+ })
+ t.Run("BlobTx", func(t *testing.T) {
+ tx := NewTx(&BlobTx{GasFeeCap: uint256.MustFromBig(gasFeeCap), GasTipCap: uint256.MustFromBig(gasTipCap)})
+ testDynamic(t, tx, nil)
+ })
+ t.Run("CeloDynamicFeeTx", func(t *testing.T) {
+ tx := NewTx(&CeloDynamicFeeTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap})
+ testDynamic(t, tx, nil)
+ tx = NewTx(&CeloDynamicFeeTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, FeeCurrency: &common.Address{}})
+ testDynamicWithFeeCurrency(t, tx, nil)
+ })
+ t.Run("CeloDynamicFeeTxV2", func(t *testing.T) {
+ tx := NewTx(&CeloDynamicFeeTxV2{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap})
+ testDynamic(t, tx, nil)
+ testDynamic(t, tx, receiptBaseFee)
+ tx = NewTx(&CeloDynamicFeeTxV2{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, FeeCurrency: &common.Address{}})
+ testDynamicWithFeeCurrency(t, tx, nil)
+ testDynamicWithFeeCurrency(t, tx, receiptBaseFee)
+ })
+ t.Run("CeloDenominatedTx", func(t *testing.T) {
+ tx := NewTx(&CeloDenominatedTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap})
+ testDynamic(t, tx, nil)
+ tx = NewTx(&CeloDenominatedTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, FeeCurrency: &common.Address{}})
+ testDynamicWithFeeCurrency(t, tx, nil)
+ })
+}
+
+func testNonDynamic(t *testing.T, tx *Transaction, receiptBaseFee *big.Int) {
+ // Non dynamic txs should always have the gas price defined in the tx.
+ config := params.TestChainConfig
+ config.GingerbreadBlock = big.NewInt(1)
+ config.LondonBlock = big.NewInt(3)
+ preGingerbreadBlock := uint64(0)
+ postGingerbreadBlock := uint64(2)
+
+ receipts := []*Receipt{{BaseFee: receiptBaseFee}}
+ txs := []*Transaction{tx}
+
+ // Pre-gingerbread
+ err := Receipts(receipts).DeriveFields(config, blockHash, preGingerbreadBlock, blockTime, nil, nil, txs)
+ require.NoError(t, err)
+ require.Equal(t, tx.GasPrice(), receipts[0].EffectiveGasPrice)
+
+ // Post-gingerbread
+ err = Receipts(receipts).DeriveFields(config, blockHash, postGingerbreadBlock, blockTime, baseFee, nil, txs)
+ require.NoError(t, err)
+ require.Equal(t, tx.GasPrice(), receipts[0].EffectiveGasPrice)
+}
+
+// Dynamic txs with no fee currency should have nil for the effective gas price pre-gingerbread and the correct
+// effective gas price post-gingerbread, if the receipt base fee is set then the post-gingerbread effective gas price
+// should be calculated with that.
+func testDynamic(t *testing.T, tx *Transaction, receiptBaseFee *big.Int) {
+ config := params.TestChainConfig
+ config.GingerbreadBlock = big.NewInt(1)
+ config.LondonBlock = big.NewInt(3)
+ preGingerbreadBlock := uint64(0)
+ postGingerbreadBlock := uint64(2)
+ receipts := []*Receipt{{BaseFee: receiptBaseFee}}
+ txs := []*Transaction{tx}
+
+ // Pre-gingerbread
+ err := Receipts(receipts).DeriveFields(config, blockHash, preGingerbreadBlock, blockTime, nil, nil, txs)
+ require.NoError(t, err)
+ var nilBigInt *big.Int
+ require.Equal(t, nilBigInt, receipts[0].EffectiveGasPrice)
+
+ // Post-gingerbread
+ err = Receipts(receipts).DeriveFields(config, blockHash, postGingerbreadBlock, blockTime, baseFee, nil, txs)
+ require.NoError(t, err)
+ if receiptBaseFee != nil {
+ require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), receiptBaseFee), receipts[0].EffectiveGasPrice)
+ } else {
+ require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), baseFee), receipts[0].EffectiveGasPrice)
+ }
+}
+
+// Dynamic txs with a fee currency set should have nil for the effective gas price pre and post gingerbread, unless
+// the receiptBaseFee is set, in which case the post-gingerbread effective gas price should be calculated with the
+// receiptBaseFee.
+func testDynamicWithFeeCurrency(t *testing.T, tx *Transaction, receiptBaseFee *big.Int) {
+ config := params.TestChainConfig
+ config.GingerbreadBlock = big.NewInt(1)
+ config.LondonBlock = big.NewInt(3)
+ preGingerbreadBlock := uint64(0)
+ postGingerbreadBlock := uint64(2)
+ receipts := []*Receipt{{BaseFee: receiptBaseFee}}
+ txs := []*Transaction{tx}
+
+ // Pre-gingerbread
+ err := Receipts(receipts).DeriveFields(config, blockHash, preGingerbreadBlock, blockTime, nil, nil, txs)
+ require.NoError(t, err)
+ var nilBigInt *big.Int
+ require.Equal(t, nilBigInt, receipts[0].EffectiveGasPrice)
+
+ // Post-gingerbread
+ err = Receipts(receipts).DeriveFields(config, blockHash, postGingerbreadBlock, blockTime, baseFee, nil, txs)
+ require.NoError(t, err)
+ if receiptBaseFee != nil {
+ require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), receiptBaseFee), receipts[0].EffectiveGasPrice)
+ } else if tx.Type() == CeloDenominatedTxType {
+ require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), baseFee), receipts[0].EffectiveGasPrice)
+ } else {
+ require.Equal(t, nilBigInt, receipts[0].EffectiveGasPrice)
+ }
+}
diff --git op-geth/core/types/celo_transaction.go Celo/core/types/celo_transaction.go
new file mode 100644
index 0000000000000000000000000000000000000000..004b976119f6895823ade91ee3f8b9ea4c1aa513
--- /dev/null
+++ Celo/core/types/celo_transaction.go
@@ -0,0 +1,144 @@
+// Copyright 2024 The Celo Authors
+// This file is part of the celo library.
+//
+// The celo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The celo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the celo library. If not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+)
+
+// IsCeloLegacy returns true if the transaction is a legacy celo transaction.
+// I.E. it has the fields feeCurrency, gatewayFee and gatewayFeeRecipient.
+func (tx *Transaction) IsCeloLegacy() bool {
+ switch t := tx.inner.(type) {
+ case *LegacyTx:
+ return t.CeloLegacy
+ }
+ return false
+}
+
+// FeeCurrency returns the fee currency of the transaction if there is one.
+func (tx *Transaction) FeeCurrency() *common.Address {
+ var feeCurrency *common.Address
+ switch t := tx.inner.(type) {
+ case *CeloDynamicFeeTx:
+ feeCurrency = t.FeeCurrency
+ case *CeloDynamicFeeTxV2:
+ feeCurrency = t.FeeCurrency
+ case *CeloDenominatedTx:
+ feeCurrency = t.FeeCurrency
+ case *LegacyTx:
+ feeCurrency = t.FeeCurrency
+ }
+ return feeCurrency
+}
+
+// GatewayFee returns the gateway fee of the transaction if there is one.
+// Note: this is here to support serving legacy transactions over the RPC, it should not be used in new code.
+func (tx *Transaction) GatewayFee() *big.Int {
+ var gatewayFee *big.Int
+ switch t := tx.inner.(type) {
+ case *CeloDynamicFeeTx:
+ gatewayFee = t.GatewayFee
+ case *LegacyTx:
+ gatewayFee = t.GatewayFee
+ }
+ return gatewayFee
+}
+
+// GatewayFeeRecipient returns the gateway fee recipient of the transaction if there is one.
+// Note: this is here to support serving legacy transactions over the RPC, it should not be used in new code.
+func (tx *Transaction) GatewayFeeRecipient() *common.Address {
+ var gatewayFeeRecipient *common.Address
+ switch t := tx.inner.(type) {
+ case *CeloDynamicFeeTx:
+ gatewayFeeRecipient = t.GatewayFeeRecipient
+ case *LegacyTx:
+ gatewayFeeRecipient = t.GatewayFeeRecipient
+ }
+ return gatewayFeeRecipient
+}
+
+// MaxFeeInFeeCurrency returns the maximum fee in the fee currency of the transaction if there is one.
+func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int {
+ var maxFeeInFeeCurrency *big.Int
+ switch t := tx.inner.(type) {
+ case *CeloDenominatedTx:
+ maxFeeInFeeCurrency = t.MaxFeeInFeeCurrency
+ }
+ return maxFeeInFeeCurrency
+}
+
+// CompareWithRates compares the effective gas price of two transactions according to the exchange rates and
+// the base fees in the transactions currencies.
+func CompareWithRates(a, b *Transaction, ratesAndFees *exchange.RatesAndFees) int {
+ if ratesAndFees == nil {
+ // During node startup the ratesAndFees might not be yet setup, compare nominally
+ feeCapCmp := a.GasFeeCapCmp(b)
+ if feeCapCmp != 0 {
+ return feeCapCmp
+ }
+ return a.GasTipCapCmp(b)
+ }
+ rates := ratesAndFees.Rates
+ if ratesAndFees.HasBaseFee() {
+ tipA := a.EffectiveGasTipValue(ratesAndFees.GetBaseFeeIn(a.FeeCurrency()))
+ tipB := b.EffectiveGasTipValue(ratesAndFees.GetBaseFeeIn(b.FeeCurrency()))
+ c, _ := exchange.CompareValue(rates, tipA, a.FeeCurrency(), tipB, b.FeeCurrency())
+ return c
+ }
+
+ // Compare fee caps if baseFee is not specified or effective tips are equal
+ feeA := a.inner.gasFeeCap()
+ feeB := b.inner.gasFeeCap()
+ c, _ := exchange.CompareValue(rates, feeA, a.FeeCurrency(), feeB, b.FeeCurrency())
+ if c != 0 {
+ return c
+ }
+
+ // Compare tips if effective tips and fee caps are equal
+ tipCapA := a.inner.gasTipCap()
+ tipCapB := b.inner.gasTipCap()
+ c, _ = exchange.CompareValue(rates, tipCapA, a.FeeCurrency(), tipCapB, b.FeeCurrency())
+ return c
+}
+
+func copyBigInt(b *big.Int) *big.Int {
+ if b == nil {
+ return nil
+ }
+ return new(big.Int).Set(b)
+}
+
+// SetYNullStyleBigIfZero is for use by tests only, it sets the Y (AKA V) field of the transaction to big.NewInt(0)
+// which internally ensures that the abs field of the big int is null as opposed to an empty slice. The reason for doing
+// this is to facilitate direct deep equal comparisons of transactions, which although they may share the same value for
+// V have different internal representations.
+func SetYNullStyleBigIfZero(tx *Transaction) {
+ switch itx := tx.inner.(type) {
+ case *DynamicFeeTx:
+ if itx.V.Sign() == 0 {
+ itx.V = big.NewInt(0)
+ }
+ case *AccessListTx:
+ if itx.V.Sign() == 0 {
+ itx.V = big.NewInt(0)
+ }
+ }
+}
diff --git op-geth/core/types/celo_transaction_marshalling.go Celo/core/types/celo_transaction_marshalling.go
new file mode 100644
index 0000000000000000000000000000000000000000..be0e7cf6848b7c378b11006d990782cde4bc612f
--- /dev/null
+++ Celo/core/types/celo_transaction_marshalling.go
@@ -0,0 +1,366 @@
+// Copyright 2024 The Celo Authors
+// This file is part of the celo library.
+//
+// The celo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The celo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the celo library. If not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+func celoTransactionMarshal(tx *Transaction) ([]byte, bool, error) {
+ var enc txJSON
+ // These are set for all tx types.
+ enc.Hash = tx.Hash()
+ enc.Type = hexutil.Uint64(tx.Type())
+ switch itx := tx.inner.(type) {
+ case *LegacyTx:
+ if !itx.CeloLegacy {
+ return nil, false, nil
+ }
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.To = tx.To()
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.Value = (*hexutil.Big)(itx.Value)
+ enc.Input = (*hexutil.Bytes)(&itx.Data)
+ enc.V = (*hexutil.Big)(itx.V)
+ enc.R = (*hexutil.Big)(itx.R)
+ enc.S = (*hexutil.Big)(itx.S)
+ if tx.Protected() {
+ enc.ChainID = (*hexutil.Big)(tx.ChainId())
+ }
+ // Celo specific fields
+ enc.FeeCurrency = itx.FeeCurrency
+ enc.GatewayFee = (*hexutil.Big)(itx.GatewayFee)
+ enc.GatewayFeeRecipient = itx.GatewayFeeRecipient
+ enc.EthCompatible = new(bool)
+ case *CeloDynamicFeeTx:
+ enc.ChainID = (*hexutil.Big)(itx.ChainID)
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.To = tx.To()
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap)
+ enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap)
+ enc.Value = (*hexutil.Big)(itx.Value)
+ enc.Input = (*hexutil.Bytes)(&itx.Data)
+ enc.AccessList = &itx.AccessList
+ enc.V = (*hexutil.Big)(itx.V)
+ enc.R = (*hexutil.Big)(itx.R)
+ enc.S = (*hexutil.Big)(itx.S)
+ // Celo specific fields
+ enc.FeeCurrency = itx.FeeCurrency
+ enc.GatewayFee = (*hexutil.Big)(itx.GatewayFee)
+ enc.GatewayFeeRecipient = itx.GatewayFeeRecipient
+ case *CeloDynamicFeeTxV2:
+ enc.ChainID = (*hexutil.Big)(itx.ChainID)
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.To = tx.To()
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap)
+ enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap)
+ enc.Value = (*hexutil.Big)(itx.Value)
+ enc.Input = (*hexutil.Bytes)(&itx.Data)
+ enc.AccessList = &itx.AccessList
+ enc.V = (*hexutil.Big)(itx.V)
+ enc.R = (*hexutil.Big)(itx.R)
+ enc.S = (*hexutil.Big)(itx.S)
+ // Celo specific fields
+ enc.FeeCurrency = itx.FeeCurrency
+ case *CeloDenominatedTx:
+ enc.ChainID = (*hexutil.Big)(itx.ChainID)
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.To = tx.To()
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap)
+ enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap)
+ enc.Value = (*hexutil.Big)(itx.Value)
+ enc.Input = (*hexutil.Bytes)(&itx.Data)
+ enc.AccessList = &itx.AccessList
+ enc.V = (*hexutil.Big)(itx.V)
+ enc.R = (*hexutil.Big)(itx.R)
+ enc.S = (*hexutil.Big)(itx.S)
+ // Celo specific fields
+ enc.FeeCurrency = itx.FeeCurrency
+ enc.MaxFeeInFeeCurrency = (*hexutil.Big)(itx.MaxFeeInFeeCurrency)
+ default:
+ return nil, false, nil
+ }
+ bytes, err := json.Marshal(&enc)
+ return bytes, true, err
+}
+
+func celoTransactionUnmarshal(dec txJSON, inner *TxData) (bool, error) {
+ switch dec.Type {
+ case LegacyTxType:
+ // EthCompatible is only set to false for celo legacy transactions in the op-geth codebase, otherwise its not
+ // set. So not set means it is ethCompatible. However in order to support correct unmarshaling of a celo legacy
+ // transaction, retrieved from a celo node (for the purposes of running our api compatibility test) we also need to
+ // handle the case where EthCompatible is set to true.
+ // TODO update this check after all chains have been migrated to be L2s see - https://github.com/celo-org/op-geth/issues/237
+ if dec.EthCompatible == nil || *dec.EthCompatible {
+ return false, nil
+ }
+ var itx LegacyTx
+ *inner = &itx
+ if dec.Nonce == nil {
+ return true, errors.New("missing required field 'nonce' in transaction")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.To != nil {
+ itx.To = dec.To
+ }
+ if dec.Gas == nil {
+ return true, errors.New("missing required field 'gas' in transaction")
+ }
+ itx.Gas = uint64(*dec.Gas)
+ if dec.GasPrice == nil {
+ return true, errors.New("missing required field 'gasPrice' in transaction")
+ }
+ itx.GasPrice = (*big.Int)(dec.GasPrice)
+ if dec.Value == nil {
+ return true, errors.New("missing required field 'value' in transaction")
+ }
+ itx.Value = (*big.Int)(dec.Value)
+ if dec.Input == nil {
+ return true, errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+
+ // signature R
+ if dec.R == nil {
+ return true, errors.New("missing required field 'r' in transaction")
+ }
+ itx.R = (*big.Int)(dec.R)
+ // signature S
+ if dec.S == nil {
+ return true, errors.New("missing required field 's' in transaction")
+ }
+ itx.S = (*big.Int)(dec.S)
+ // signature V
+ if dec.V == nil {
+ return true, errors.New("missing required field 'v' in transaction")
+ }
+ itx.V = (*big.Int)(dec.V)
+ if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
+ if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil {
+ return true, err
+ }
+ }
+ itx.CeloLegacy = true
+ itx.FeeCurrency = dec.FeeCurrency
+ itx.GatewayFeeRecipient = dec.GatewayFeeRecipient
+ itx.GatewayFee = (*big.Int)(dec.GatewayFee)
+
+ case CeloDynamicFeeTxType:
+ var itx CeloDynamicFeeTx
+ *inner = &itx
+ if dec.ChainID == nil {
+ return true, errors.New("missing required field 'chainId' in transaction")
+ }
+ itx.ChainID = (*big.Int)(dec.ChainID)
+ if dec.Nonce == nil {
+ return true, errors.New("missing required field 'nonce' in transaction")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.To != nil {
+ itx.To = dec.To
+ }
+ if dec.Gas == nil {
+ return true, errors.New("missing required field 'gas' for txdata")
+ }
+ itx.Gas = uint64(*dec.Gas)
+ if dec.MaxPriorityFeePerGas == nil {
+ return true, errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
+ }
+ itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas)
+ if dec.MaxFeePerGas == nil {
+ return true, errors.New("missing required field 'maxFeePerGas' for txdata")
+ }
+ itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
+ if dec.Value == nil {
+ return true, errors.New("missing required field 'value' in transaction")
+ }
+ itx.Value = (*big.Int)(dec.Value)
+ if dec.Input == nil {
+ return true, errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+ if dec.V == nil {
+ return true, errors.New("missing required field 'v' in transaction")
+ }
+ if dec.AccessList != nil {
+ itx.AccessList = *dec.AccessList
+ }
+ itx.V = (*big.Int)(dec.V)
+ if dec.R == nil {
+ return true, errors.New("missing required field 'r' in transaction")
+ }
+ itx.R = (*big.Int)(dec.R)
+ if dec.S == nil {
+ return true, errors.New("missing required field 's' in transaction")
+ }
+ itx.S = (*big.Int)(dec.S)
+ withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
+ if withSignature {
+ if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
+ return true, err
+ }
+ }
+ // Celo specific fields
+ itx.FeeCurrency = dec.FeeCurrency
+ itx.GatewayFee = (*big.Int)(dec.GatewayFee)
+ itx.GatewayFeeRecipient = dec.GatewayFeeRecipient
+ case CeloDynamicFeeTxV2Type:
+ var itx CeloDynamicFeeTxV2
+ *inner = &itx
+ if dec.ChainID == nil {
+ return true, errors.New("missing required field 'chainId' in transaction")
+ }
+ itx.ChainID = (*big.Int)(dec.ChainID)
+ if dec.Nonce == nil {
+ return true, errors.New("missing required field 'nonce' in transaction")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.To != nil {
+ itx.To = dec.To
+ }
+ if dec.Gas == nil {
+ return true, errors.New("missing required field 'gas' for txdata")
+ }
+ itx.Gas = uint64(*dec.Gas)
+ if dec.MaxPriorityFeePerGas == nil {
+ return true, errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
+ }
+ itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas)
+ if dec.MaxFeePerGas == nil {
+ return true, errors.New("missing required field 'maxFeePerGas' for txdata")
+ }
+ itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
+ if dec.Value == nil {
+ return true, errors.New("missing required field 'value' in transaction")
+ }
+ itx.Value = (*big.Int)(dec.Value)
+ if dec.Input == nil {
+ return true, errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+ if dec.V == nil {
+ return true, errors.New("missing required field 'v' in transaction")
+ }
+ if dec.AccessList != nil {
+ itx.AccessList = *dec.AccessList
+ }
+ itx.V = (*big.Int)(dec.V)
+ if dec.R == nil {
+ return true, errors.New("missing required field 'r' in transaction")
+ }
+ itx.R = (*big.Int)(dec.R)
+ if dec.S == nil {
+ return true, errors.New("missing required field 's' in transaction")
+ }
+ itx.S = (*big.Int)(dec.S)
+ withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
+ if withSignature {
+ if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
+ return true, err
+ }
+ }
+ // Celo specific fields
+ itx.FeeCurrency = dec.FeeCurrency
+ case CeloDenominatedTxType:
+ var itx CeloDenominatedTx
+ *inner = &itx
+ if dec.ChainID == nil {
+ return true, errors.New("missing required field 'chainId' in transaction")
+ }
+ itx.ChainID = (*big.Int)(dec.ChainID)
+ if dec.Nonce == nil {
+ return true, errors.New("missing required field 'nonce' in transaction")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.To != nil {
+ itx.To = dec.To
+ }
+ if dec.Gas == nil {
+ return true, errors.New("missing required field 'gas' for txdata")
+ }
+ itx.Gas = uint64(*dec.Gas)
+ if dec.MaxPriorityFeePerGas == nil {
+ return true, errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
+ }
+ itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas)
+ if dec.MaxFeePerGas == nil {
+ return true, errors.New("missing required field 'maxFeePerGas' for txdata")
+ }
+ itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
+ if dec.Value == nil {
+ return true, errors.New("missing required field 'value' in transaction")
+ }
+ itx.FeeCurrency = dec.FeeCurrency
+ itx.Value = (*big.Int)(dec.Value)
+ if dec.Input == nil {
+ return true, errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+ if dec.V == nil {
+ return true, errors.New("missing required field 'v' in transaction")
+ }
+ if dec.AccessList != nil {
+ itx.AccessList = *dec.AccessList
+ }
+ itx.V = (*big.Int)(dec.V)
+ if dec.R == nil {
+ return true, errors.New("missing required field 'r' in transaction")
+ }
+ itx.R = (*big.Int)(dec.R)
+ if dec.S == nil {
+ return true, errors.New("missing required field 's' in transaction")
+ }
+ itx.S = (*big.Int)(dec.S)
+ withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
+ if withSignature {
+ if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
+ return true, err
+ }
+ }
+ // Celo specific fields
+ itx.FeeCurrency = dec.FeeCurrency
+ itx.MaxFeeInFeeCurrency = (*big.Int)(dec.MaxFeeInFeeCurrency)
+ default:
+ return false, nil
+ }
+
+ return true, nil
+}
+
+func celoDecodeTyped(b []byte) (TxData, bool, error) {
+ var inner TxData
+ switch b[0] {
+ case CeloDenominatedTxType:
+ inner = new(CeloDenominatedTx)
+ case CeloDynamicFeeTxV2Type:
+ inner = new(CeloDynamicFeeTxV2)
+ case CeloDynamicFeeTxType:
+ inner = new(CeloDynamicFeeTx)
+ default:
+ return nil, false, nil
+ }
+ err := inner.decode(b[1:])
+ return inner, true, err
+}
diff --git op-geth/core/types/celo_transaction_signing_forks.go Celo/core/types/celo_transaction_signing_forks.go
new file mode 100644
index 0000000000000000000000000000000000000000..870fc0a8dd4dbfba6b96c3262f426080b32fa64a
--- /dev/null
+++ Celo/core/types/celo_transaction_signing_forks.go
@@ -0,0 +1,116 @@
+package types
+
+import "github.com/ethereum/go-ethereum/params"
+
+var (
+ // celoForks is the list of celo forks that are supported by the
+ // celoSigner. This list is ordered with more recent forks appearing
+ // earlier. It is assumed that if a more recent fork is active then all
+ // previous forks are also active.
+ celoForks = forks{&cel2{}, &celoLegacy{}}
+)
+
+type forks []fork
+
+// activeForks returns the active forks for the given block time and chain config.
+func (f forks) activeForks(blockTime uint64, config *params.ChainConfig) []fork {
+ for i, fork := range f {
+ if fork.active(blockTime, config) {
+ return f[i:]
+ }
+ }
+ return nil
+}
+
+// findTxFuncs returns the txFuncs for the given tx if there is a fork that supports it.
+func (f forks) findTxFuncs(tx *Transaction) *txFuncs {
+ for _, fork := range f {
+ if funcs := fork.txFuncs(tx); funcs != nil {
+ return funcs
+ }
+ }
+ return nil
+}
+
+// fork contains functionality to determine if it is active for a given block
+// time and chain config. It also acts as a container for functionality related
+// to transactions enabled or deprecated in that fork.
+type fork interface {
+ // active returns true if the fork is active at the given block time.
+ active(blockTime uint64, config *params.ChainConfig) bool
+ // equal returns true if the given fork is the same underlying type as this fork.
+ equal(fork) bool
+ // txFuncs returns the txFuncs for the given tx if it is supported by the
+ // fork. If a fork deprecates a tx type then this function should return
+ // deprecatedTxFuncs for that tx type.
+ txFuncs(tx *Transaction) *txFuncs
+}
+
+// Cel2 is the fork marking the transition point from an L1 to an L2.
+// It deprecates CeloDynamicFeeTxType and LegacyTxTypes with CeloLegacy set to true.
+type cel2 struct{}
+
+func (c *cel2) active(blockTime uint64, config *params.ChainConfig) bool {
+ return config.IsCel2(blockTime)
+}
+
+func (c *cel2) equal(other fork) bool {
+ _, ok := other.(*cel2)
+ return ok
+}
+
+func (c *cel2) txFuncs(tx *Transaction) *txFuncs {
+ t := tx.Type()
+ switch {
+ case t == LegacyTxType && tx.IsCeloLegacy():
+ return deprecatedTxFuncs
+ case t == CeloDynamicFeeTxType:
+ return deprecatedTxFuncs
+ }
+ return nil
+}
+
+// celoLegacy isn't actually a fork, but a placeholder for all historical celo
+// related forks occurring on the celo L1. We don't need to construct the full
+// signer chain from the celo legacy project because we won't support
+// historical transaction execution, so we just need to be able to derive the
+// senders for historical transactions and since we assume that the historical
+// data is correct we just need one blanket signer that can cover all legacy
+// celo transactions, before the L2 transition point.
+type celoLegacy struct{}
+
+func (c *celoLegacy) active(blockTime uint64, config *params.ChainConfig) bool {
+ // The celo legacy fork is always active in a celo context
+ return config.Cel2Time != nil
+}
+
+func (c *celoLegacy) equal(other fork) bool {
+ _, ok := other.(*cel2)
+ return ok
+}
+
+func (c *celoLegacy) txFuncs(tx *Transaction) *txFuncs {
+ t := tx.Type()
+ switch {
+ case t == uint8(LegacyTxType) && tx.IsCeloLegacy():
+ return celoLegacyTxFuncs
+ case t == DynamicFeeTxType:
+ // We handle the dynamic fee tx type here because we need to handle
+ // migrated dynamic fee txs. These were enabeled in celo in the Espresso
+ // hardfork, which doesn't have any analogue in op-geth. Even though
+ // op-geth does enable support for dynamic fee txs in the London
+ // hardfork (which we set to the cel2 block) that fork contains a lot of
+ // changes that were not part of Espresso. So instead we ned to handle
+ // DynamicFeeTxTypes here.
+ return dynamicFeeTxFuncs
+ case t == AccessListTxType:
+ // Similar to the dynamic fee tx type, we need to handle the access list tx type that was also enabled by the
+ // espresso hardfork.
+ return accessListTxFuncs
+ case t == CeloDynamicFeeTxV2Type:
+ return celoDynamicFeeTxV2Funcs
+ case t == CeloDynamicFeeTxType:
+ return celoDynamicFeeTxFuncs
+ }
+ return nil
+}
diff --git op-geth/core/types/celo_transaction_signing_test.go Celo/core/types/celo_transaction_signing_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ac1a8852e243612970e6b1459edf74573fc8da08
--- /dev/null
+++ Celo/core/types/celo_transaction_signing_test.go
@@ -0,0 +1,124 @@
+package types
+
+import (
+ "crypto/rand"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/require"
+)
+
+// Tests that by default the celo legacy signer will sign transactions in a protected manner.
+func TestProtectedCeloLegacyTxSigning(t *testing.T) {
+ tx := newCeloTx(t)
+ // Configure config and block time to enable the celoLegacy signer, legacy
+ // transactions are deprecated after cel2
+ cel2Time := uint64(2000)
+ config := ¶ms.ChainConfig{
+ ChainID: big.NewInt(10000),
+ Cel2Time: &cel2Time,
+ }
+ number := new(big.Int).SetUint64(100)
+ blockTime := uint64(1000)
+ s := MakeSigner(config, number, blockTime)
+
+ senderKey, err := crypto.GenerateKey()
+ require.NoError(t, err)
+ signed, err := SignTx(tx, s, senderKey)
+ require.NoError(t, err)
+
+ // Check the sender just to be sure that the signing worked correctly
+ actualSender, err := Sender(s, signed)
+ require.NoError(t, err)
+ require.Equal(t, crypto.PubkeyToAddress(senderKey.PublicKey), actualSender)
+ // Validate that the transaction is protected
+ require.True(t, signed.Protected())
+}
+
+// Tests that the celo legacy signer can still derive the sender of an unprotected transaction.
+func TestUnprotectedCeloLegacyTxSenderDerivation(t *testing.T) {
+ tx := newCeloTx(t)
+ // Configure config and block time to enable the celoLegacy signer, legacy
+ // transactions are deprecated after cel2
+ cel2Time := uint64(2000)
+ config := ¶ms.ChainConfig{
+ ChainID: big.NewInt(10000),
+ Cel2Time: &cel2Time,
+ }
+ number := new(big.Int).SetUint64(100)
+ blockTime := uint64(1000)
+ s := MakeSigner(config, number, blockTime)
+ u := &unprotectedSigner{config.ChainID}
+
+ senderKey, err := crypto.GenerateKey()
+ require.NoError(t, err)
+ // Sign unprotected
+ signed, err := SignTx(tx, u, senderKey)
+ require.NoError(t, err)
+
+ // Check that the sender can be derived with the signer from MakeSigner
+ actualSender, err := Sender(s, signed)
+ require.NoError(t, err)
+ require.Equal(t, crypto.PubkeyToAddress(senderKey.PublicKey), actualSender)
+ // Validate that the transaction is not protected
+ require.False(t, signed.Protected())
+}
+
+func newCeloTx(t *testing.T) *Transaction {
+ return NewTx(&LegacyTx{
+ Nonce: 1,
+ GasPrice: new(big.Int).SetUint64(10000),
+ Gas: 100000,
+
+ FeeCurrency: randomAddress(t),
+ GatewayFee: new(big.Int).SetUint64(100),
+ GatewayFeeRecipient: randomAddress(t),
+
+ To: randomAddress(t),
+ Value: new(big.Int).SetUint64(1000),
+ Data: []byte{},
+
+ CeloLegacy: true,
+ })
+}
+
+func randomAddress(t *testing.T) *common.Address {
+ addr := common.Address{}
+ _, err := rand.Read(addr[:])
+ require.NoError(t, err)
+ return &addr
+}
+
+// This signer mimics Homestead signing but for Celo transactions
+type unprotectedSigner struct {
+ chainID *big.Int
+}
+
+// ChainID implements Signer.
+func (u *unprotectedSigner) ChainID() *big.Int {
+ return u.chainID
+}
+
+// Equal implements Signer.
+func (u *unprotectedSigner) Equal(Signer) bool {
+ panic("unimplemented")
+}
+
+// Hash implements Signer.
+func (u *unprotectedSigner) Hash(tx *Transaction) common.Hash {
+ return rlpHash(baseCeloLegacyTxSigningFields(tx))
+}
+
+// Sender implements Signer.
+func (u *unprotectedSigner) Sender(tx *Transaction) (common.Address, error) {
+ panic("unimplemented")
+}
+
+// SignatureValues implements Signer.
+func (u *unprotectedSigner) SignatureValues(tx *Transaction, sig []byte) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ r, s, v = decodeSignature(sig)
+ return r, s, v, nil
+}
diff --git op-geth/core/types/celo_transaction_signing_tx_funcs.go Celo/core/types/celo_transaction_signing_tx_funcs.go
new file mode 100644
index 0000000000000000000000000000000000000000..a0e55c72e9da15d785cc8a2f55bd84988d6a2413
--- /dev/null
+++ Celo/core/types/celo_transaction_signing_tx_funcs.go
@@ -0,0 +1,205 @@
+package types
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var (
+
+ // deprecatedTxFuncs should be returned by forks that have deprecated support for a tx type.
+ deprecatedTxFuncs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return tx.Hash()
+ },
+ signatureValues: func(tx *Transaction, sig []byte, signerChainID *big.Int) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ return nil, nil, nil, fmt.Errorf("%w %v", ErrDeprecatedTxType, tx.Type())
+ },
+ sender: func(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error) {
+ if tx.IsCeloLegacy() {
+ return common.Address{}, fmt.Errorf("%w %v %v", ErrDeprecatedTxType, tx.Type(), "(celo legacy)")
+ }
+ return common.Address{}, fmt.Errorf("%w %v", ErrDeprecatedTxType, tx.Type())
+ },
+ }
+
+ // Although celo allowed unprotected transactions it never supported signing
+ // them with signers retrieved by MakeSigner or LatestSigner (if you wanted
+ // to make an unprotected transaction you needed to use the HomesteadSigner
+ // directly), so both hash and signatureValues functions here provide
+ // protected values, but sender can accept unprotected transactions. See
+ // https://github.com/celo-org/celo-blockchain/pull/1748/files and
+ // https://github.com/celo-org/celo-blockchain/issues/1734 and
+ // https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0050.md
+ celoLegacyTxFuncs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return rlpHash(append(baseCeloLegacyTxSigningFields(tx), chainID, uint(0), uint(0)))
+ },
+ signatureValues: func(tx *Transaction, sig []byte, signerChainID *big.Int) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ r, s, v = decodeSignature(sig)
+ if signerChainID.Sign() != 0 {
+ v = big.NewInt(int64(sig[64] + 35))
+ signerChainMul := new(big.Int).Mul(signerChainID, big.NewInt(2))
+ v.Add(v, signerChainMul)
+ }
+ return r, s, v, nil
+ },
+ sender: func(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error) {
+ if tx.Protected() {
+ if tx.ChainId().Cmp(signerChainID) != 0 {
+ return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), signerChainID)
+ }
+ v, r, s := tx.RawSignatureValues()
+ signerChainMul := new(big.Int).Mul(signerChainID, big.NewInt(2))
+ v = new(big.Int).Sub(v, signerChainMul)
+ v.Sub(v, big8)
+ return recoverPlain(hashFunc(tx, signerChainID), r, s, v, true)
+ } else {
+ v, r, s := tx.RawSignatureValues()
+ return recoverPlain(rlpHash(baseCeloLegacyTxSigningFields(tx)), r, s, v, true)
+ }
+ },
+ }
+
+ accessListTxFuncs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return NewEIP2930Signer(chainID).Hash(tx)
+ },
+ signatureValues: func(tx *Transaction, sig []byte, signerChainID *big.Int) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ return NewEIP2930Signer(signerChainID).SignatureValues(tx, sig)
+ },
+ sender: func(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error) {
+ return NewEIP2930Signer(tx.ChainId()).Sender(tx)
+ },
+ }
+
+ dynamicFeeTxFuncs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return NewLondonSigner(chainID).Hash(tx)
+ },
+ signatureValues: func(tx *Transaction, sig []byte, signerChainID *big.Int) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ return NewLondonSigner(signerChainID).SignatureValues(tx, sig)
+ },
+ sender: func(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error) {
+ return NewLondonSigner(signerChainID).Sender(tx)
+ },
+ }
+
+ celoDynamicFeeTxFuncs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return prefixedRlpHash(
+ tx.Type(),
+ []interface{}{
+ chainID,
+ tx.Nonce(),
+ tx.GasTipCap(),
+ tx.GasFeeCap(),
+ tx.Gas(),
+ tx.FeeCurrency(),
+ tx.GatewayFeeRecipient(),
+ tx.GatewayFee(),
+ tx.To(),
+ tx.Value(),
+ tx.Data(),
+ tx.AccessList(),
+ })
+ },
+ signatureValues: dynamicAndDenominatedTxSigValues,
+ sender: dynamicAndDenominatedTxSender,
+ }
+
+ // Custom signing functionality for CeloDynamicFeeTxV2 txs.
+ celoDynamicFeeTxV2Funcs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return prefixedRlpHash(tx.Type(), baseDynomicatedTxSigningFields(tx, chainID))
+ },
+ signatureValues: dynamicAndDenominatedTxSigValues,
+ sender: dynamicAndDenominatedTxSender,
+ }
+
+ // Custom signing functionality for CeloDenominatedTx txs.
+ //
+ // TODO remove this nolint directive when we do enable support for cip66 transactions.
+ //nolint:unused
+ celoDenominatedTxFuncs = &txFuncs{
+ hash: func(tx *Transaction, chainID *big.Int) common.Hash {
+ return prefixedRlpHash(tx.Type(), append(baseDynomicatedTxSigningFields(tx, chainID), tx.MaxFeeInFeeCurrency()))
+ },
+ signatureValues: dynamicAndDenominatedTxSigValues,
+ sender: dynamicAndDenominatedTxSender,
+ }
+)
+
+// txFuncs serves as a container to hold custom signing functionality for a transaction.
+//
+// TODO consider changing this to an interface, it might make things easier
+// because then I could store custom bits of data relevant to each tx type /
+// signer such as the signerChainMul. It would also solve the problem of having
+// to pass the hash function into the sender function.
+type txFuncs struct {
+ hash func(tx *Transaction, chainID *big.Int) common.Hash
+ signatureValues func(tx *Transaction, sig []byte, signerChainID *big.Int) (r *big.Int, s *big.Int, v *big.Int, err error)
+ sender func(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error)
+}
+
+// Returns the signature values for CeloDynamicFeeTxV2 and CeloDenominatedTx
+// transactions.
+func dynamicAndDenominatedTxSigValues(tx *Transaction, sig []byte, signerChainID *big.Int) (r *big.Int, s *big.Int, v *big.Int, err error) {
+ // Check that chain ID of tx matches the signer. We also accept ID zero here,
+ // because it indicates that the chain ID was not specified in the tx.
+ chainID := tx.inner.chainID()
+ if chainID.Sign() != 0 && chainID.Cmp(signerChainID) != 0 {
+ return nil, nil, nil, ErrInvalidChainId
+ }
+ r, s, _ = decodeSignature(sig)
+ v = big.NewInt(int64(sig[64]))
+ return r, s, v, nil
+}
+
+// Returns the sender for CeloDynamicFeeTxV2 and CeloDenominatedTx
+// transactions.
+func dynamicAndDenominatedTxSender(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error) {
+ if tx.ChainId().Cmp(signerChainID) != 0 {
+ return common.Address{}, ErrInvalidChainId
+ }
+ V, R, S := tx.RawSignatureValues()
+ // DynamicFee txs are defined to use 0 and 1 as their recovery
+ // id, add 27 to become equivalent to unprotected Homestead signatures.
+ V = new(big.Int).Add(V, big.NewInt(27))
+ return recoverPlain(hashFunc(tx, signerChainID), R, S, V, true)
+}
+
+// Extracts the common signing fields for CeloLegacy and CeloDynamicFeeTx
+// transactions.
+func baseCeloLegacyTxSigningFields(tx *Transaction) []interface{} {
+ return []interface{}{
+ tx.Nonce(),
+ tx.GasPrice(),
+ tx.Gas(),
+ tx.FeeCurrency(),
+ tx.GatewayFeeRecipient(),
+ tx.GatewayFee(),
+ tx.To(),
+ tx.Value(),
+ tx.Data(),
+ }
+}
+
+// Extracts the common signing fields for CeloDynamicFeeTxV2 and
+// CeloDenominatedTx transactions.
+func baseDynomicatedTxSigningFields(tx *Transaction, chainID *big.Int) []interface{} {
+ return []interface{}{
+ chainID,
+ tx.Nonce(),
+ tx.GasTipCap(),
+ tx.GasFeeCap(),
+ tx.Gas(),
+ tx.To(),
+ tx.Value(),
+ tx.Data(),
+ tx.AccessList(),
+ tx.FeeCurrency(),
+ }
+}
diff --git op-geth/core/types/celo_tx_legacy.go Celo/core/types/celo_tx_legacy.go
new file mode 100644
index 0000000000000000000000000000000000000000..8de6127523b2abe7f355f636aae65afbc63d21ec
--- /dev/null
+++ Celo/core/types/celo_tx_legacy.go
@@ -0,0 +1,162 @@
+// Copyright 2024 The Celo Authors
+// This file is part of the celo library.
+//
+// The celo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The celo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the celo library. If not, see <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "io"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+var ethCompatibleTxNumFields = 9
+
+// ethCompatibleTxRlpList is used for RLP encoding/decoding of eth-compatible transactions.
+// As such, it:
+// (a) excludes the Celo-only fields,
+// (b) doesn't need the Hash or EthCompatible fields, and
+// (c) doesn't need the `json` or `gencodec` tags
+type ethCompatibleTxRlpList struct {
+ Nonce uint64 // nonce of sender account
+ GasPrice *big.Int // wei per gas
+ Gas uint64 // gas limit
+ To *common.Address `rlp:"nil"` // nil means contract creation
+ Value *big.Int // wei amount
+ Data []byte // contract invocation input data
+ V, R, S *big.Int // signature values
+}
+
+// celoTxRlpList is used for RLP encoding/decoding of celo transactions.
+type celoTxRlpList struct {
+ Nonce uint64 // nonce of sender account
+ GasPrice *big.Int // wei per gas
+ Gas uint64 // gas limit
+ FeeCurrency *common.Address `rlp:"nil"` // nil means native currency
+ GatewayFeeRecipient *common.Address `rlp:"nil"` // nil means no gateway fee is paid
+ GatewayFee *big.Int `rlp:"nil"`
+ To *common.Address `rlp:"nil"` // nil means contract creation
+ Value *big.Int // wei amount
+ Data []byte // contract invocation input data
+ V, R, S *big.Int // signature values
+}
+
+func toEthCompatibleRlpList(tx LegacyTx) ethCompatibleTxRlpList {
+ return ethCompatibleTxRlpList{
+ Nonce: tx.Nonce,
+ GasPrice: tx.GasPrice,
+ Gas: tx.Gas,
+ To: tx.To,
+ Value: tx.Value,
+ Data: tx.Data,
+ V: tx.V,
+ R: tx.R,
+ S: tx.S,
+ }
+}
+
+func toCeloRlpList(tx LegacyTx) celoTxRlpList {
+ return celoTxRlpList{
+ Nonce: tx.Nonce,
+ GasPrice: tx.GasPrice,
+ Gas: tx.Gas,
+ To: tx.To,
+ Value: tx.Value,
+ Data: tx.Data,
+ V: tx.V,
+ R: tx.R,
+ S: tx.S,
+
+ // Celo specific fields
+ FeeCurrency: tx.FeeCurrency,
+ GatewayFeeRecipient: tx.GatewayFeeRecipient,
+ GatewayFee: tx.GatewayFee,
+ }
+}
+
+func setTxFromEthCompatibleRlpList(tx *LegacyTx, rlplist ethCompatibleTxRlpList) {
+ tx.Nonce = rlplist.Nonce
+ tx.GasPrice = rlplist.GasPrice
+ tx.Gas = rlplist.Gas
+ tx.To = rlplist.To
+ tx.Value = rlplist.Value
+ tx.Data = rlplist.Data
+ tx.V = rlplist.V
+ tx.R = rlplist.R
+ tx.S = rlplist.S
+ tx.Hash = nil // txdata.Hash is calculated and saved inside tx.Hash()
+
+ // Celo specific fields
+ tx.FeeCurrency = nil
+ tx.GatewayFeeRecipient = nil
+ tx.GatewayFee = nil
+ tx.CeloLegacy = false
+}
+
+func setTxFromCeloRlpList(tx *LegacyTx, rlplist celoTxRlpList) {
+ tx.Nonce = rlplist.Nonce
+ tx.GasPrice = rlplist.GasPrice
+ tx.Gas = rlplist.Gas
+ tx.To = rlplist.To
+ tx.Value = rlplist.Value
+ tx.Data = rlplist.Data
+ tx.V = rlplist.V
+ tx.R = rlplist.R
+ tx.S = rlplist.S
+ tx.Hash = nil // txdata.Hash is calculated and saved inside tx.Hash()
+
+ // Celo specific fields
+ tx.FeeCurrency = rlplist.FeeCurrency
+ tx.GatewayFeeRecipient = rlplist.GatewayFeeRecipient
+ tx.GatewayFee = rlplist.GatewayFee
+ tx.CeloLegacy = true
+}
+
+// EncodeRLP implements rlp.Encoder
+func (tx *LegacyTx) EncodeRLP(w io.Writer) error {
+ if tx.CeloLegacy {
+ return rlp.Encode(w, toCeloRlpList(*tx))
+ } else {
+ return rlp.Encode(w, toEthCompatibleRlpList(*tx))
+ }
+}
+
+// DecodeRLP implements rlp.Decoder
+func (tx *LegacyTx) DecodeRLP(s *rlp.Stream) (err error) {
+ _, size, _ := s.Kind()
+ var raw rlp.RawValue
+ err = s.Decode(&raw)
+ if err != nil {
+ return err
+ }
+ headerSize := len(raw) - int(size)
+ numElems, err := rlp.CountValues(raw[headerSize:])
+ if err != nil {
+ return err
+ }
+ if numElems == ethCompatibleTxNumFields {
+ rlpList := ethCompatibleTxRlpList{}
+ err = rlp.DecodeBytes(raw, &rlpList)
+ setTxFromEthCompatibleRlpList(tx, rlpList)
+ } else {
+ var rlpList celoTxRlpList
+ err = rlp.DecodeBytes(raw, &rlpList)
+ setTxFromCeloRlpList(tx, rlpList)
+ }
+
+ return err
+}
diff --git op-geth/core/types/gen_after_gingerbread_header_rlp.go Celo/core/types/gen_after_gingerbread_header_rlp.go
new file mode 100644
index 0000000000000000000000000000000000000000..8aa2892d2c3c458ad6cf48cb5600c54a9d408217
--- /dev/null
+++ Celo/core/types/gen_after_gingerbread_header_rlp.go
@@ -0,0 +1,246 @@
+// Code generated by rlpgen. DO NOT EDIT.
+
+package types
+
+import "github.com/ethereum/go-ethereum/common"
+import "github.com/ethereum/go-ethereum/rlp"
+import "io"
+
+func (obj *AfterGingerbreadHeader) EncodeRLP(_w io.Writer) error {
+ w := rlp.NewEncoderBuffer(_w)
+ _tmp0 := w.List()
+ w.WriteBytes(obj.ParentHash[:])
+ w.WriteBytes(obj.UncleHash[:])
+ w.WriteBytes(obj.Coinbase[:])
+ w.WriteBytes(obj.Root[:])
+ w.WriteBytes(obj.TxHash[:])
+ w.WriteBytes(obj.ReceiptHash[:])
+ w.WriteBytes(obj.Bloom[:])
+ if obj.Difficulty == nil {
+ w.Write(rlp.EmptyString)
+ } else {
+ if obj.Difficulty.Sign() == -1 {
+ return rlp.ErrNegativeBigInt
+ }
+ w.WriteBigInt(obj.Difficulty)
+ }
+ if obj.Number == nil {
+ w.Write(rlp.EmptyString)
+ } else {
+ if obj.Number.Sign() == -1 {
+ return rlp.ErrNegativeBigInt
+ }
+ w.WriteBigInt(obj.Number)
+ }
+ w.WriteUint64(obj.GasLimit)
+ w.WriteUint64(obj.GasUsed)
+ w.WriteUint64(obj.Time)
+ w.WriteBytes(obj.Extra)
+ w.WriteBytes(obj.MixDigest[:])
+ w.WriteBytes(obj.Nonce[:])
+ _tmp1 := obj.BaseFee != nil
+ _tmp2 := obj.WithdrawalsHash != nil
+ _tmp3 := obj.BlobGasUsed != nil
+ _tmp4 := obj.ExcessBlobGas != nil
+ _tmp5 := obj.ParentBeaconRoot != nil
+ _tmp6 := obj.RequestsHash != nil
+ if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
+ if obj.BaseFee == nil {
+ w.Write(rlp.EmptyString)
+ } else {
+ if obj.BaseFee.Sign() == -1 {
+ return rlp.ErrNegativeBigInt
+ }
+ w.WriteBigInt(obj.BaseFee)
+ }
+ }
+ if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
+ if obj.WithdrawalsHash == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteBytes(obj.WithdrawalsHash[:])
+ }
+ }
+ if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
+ if obj.BlobGasUsed == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteUint64((*obj.BlobGasUsed))
+ }
+ }
+ if _tmp4 || _tmp5 || _tmp6 {
+ if obj.ExcessBlobGas == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteUint64((*obj.ExcessBlobGas))
+ }
+ }
+ if _tmp5 || _tmp6 {
+ if obj.ParentBeaconRoot == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteBytes(obj.ParentBeaconRoot[:])
+ }
+ }
+ if _tmp6 {
+ if obj.RequestsHash == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteBytes(obj.RequestsHash[:])
+ }
+ }
+ w.ListEnd(_tmp0)
+ return w.Flush()
+}
+
+func (obj *AfterGingerbreadHeader) DecodeRLP(dec *rlp.Stream) error {
+ var _tmp0 AfterGingerbreadHeader
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // ParentHash:
+ var _tmp1 common.Hash
+ if err := dec.ReadBytes(_tmp1[:]); err != nil {
+ return err
+ }
+ _tmp0.ParentHash = _tmp1
+ // UncleHash:
+ var _tmp2 common.Hash
+ if err := dec.ReadBytes(_tmp2[:]); err != nil {
+ return err
+ }
+ _tmp0.UncleHash = _tmp2
+ // Coinbase:
+ var _tmp3 common.Address
+ if err := dec.ReadBytes(_tmp3[:]); err != nil {
+ return err
+ }
+ _tmp0.Coinbase = _tmp3
+ // Root:
+ var _tmp4 common.Hash
+ if err := dec.ReadBytes(_tmp4[:]); err != nil {
+ return err
+ }
+ _tmp0.Root = _tmp4
+ // TxHash:
+ var _tmp5 common.Hash
+ if err := dec.ReadBytes(_tmp5[:]); err != nil {
+ return err
+ }
+ _tmp0.TxHash = _tmp5
+ // ReceiptHash:
+ var _tmp6 common.Hash
+ if err := dec.ReadBytes(_tmp6[:]); err != nil {
+ return err
+ }
+ _tmp0.ReceiptHash = _tmp6
+ // Bloom:
+ var _tmp7 Bloom
+ if err := dec.ReadBytes(_tmp7[:]); err != nil {
+ return err
+ }
+ _tmp0.Bloom = _tmp7
+ // Difficulty:
+ _tmp8, err := dec.BigInt()
+ if err != nil {
+ return err
+ }
+ _tmp0.Difficulty = _tmp8
+ // Number:
+ _tmp9, err := dec.BigInt()
+ if err != nil {
+ return err
+ }
+ _tmp0.Number = _tmp9
+ // GasLimit:
+ _tmp10, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.GasLimit = _tmp10
+ // GasUsed:
+ _tmp11, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.GasUsed = _tmp11
+ // Time:
+ _tmp12, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.Time = _tmp12
+ // Extra:
+ _tmp13, err := dec.Bytes()
+ if err != nil {
+ return err
+ }
+ _tmp0.Extra = _tmp13
+ // MixDigest:
+ var _tmp14 common.Hash
+ if err := dec.ReadBytes(_tmp14[:]); err != nil {
+ return err
+ }
+ _tmp0.MixDigest = _tmp14
+ // Nonce:
+ var _tmp15 BlockNonce
+ if err := dec.ReadBytes(_tmp15[:]); err != nil {
+ return err
+ }
+ _tmp0.Nonce = _tmp15
+ // BaseFee:
+ if dec.MoreDataInList() {
+ _tmp16, err := dec.BigInt()
+ if err != nil {
+ return err
+ }
+ _tmp0.BaseFee = _tmp16
+ // WithdrawalsHash:
+ if dec.MoreDataInList() {
+ var _tmp17 common.Hash
+ if err := dec.ReadBytes(_tmp17[:]); err != nil {
+ return err
+ }
+ _tmp0.WithdrawalsHash = &_tmp17
+ // BlobGasUsed:
+ if dec.MoreDataInList() {
+ _tmp18, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.BlobGasUsed = &_tmp18
+ // ExcessBlobGas:
+ if dec.MoreDataInList() {
+ _tmp19, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.ExcessBlobGas = &_tmp19
+ // ParentBeaconRoot:
+ if dec.MoreDataInList() {
+ var _tmp20 common.Hash
+ if err := dec.ReadBytes(_tmp20[:]); err != nil {
+ return err
+ }
+ _tmp0.ParentBeaconRoot = &_tmp20
+ // RequestsHash:
+ if dec.MoreDataInList() {
+ var _tmp21 common.Hash
+ if err := dec.ReadBytes(_tmp21[:]); err != nil {
+ return err
+ }
+ _tmp0.RequestsHash = &_tmp21
+ }
+ }
+ }
+ }
+ }
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ *obj = _tmp0
+ return nil
+}
diff --git op-geth/core/types/gen_before_gingerbread_header_rlp.go Celo/core/types/gen_before_gingerbread_header_rlp.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a0014b48af09b103d366cf5e55882eeeb4c19e0
--- /dev/null
+++ Celo/core/types/gen_before_gingerbread_header_rlp.go
@@ -0,0 +1,105 @@
+// Code generated by rlpgen. DO NOT EDIT.
+
+package types
+
+import "github.com/ethereum/go-ethereum/common"
+import "github.com/ethereum/go-ethereum/rlp"
+import "io"
+
+func (obj *BeforeGingerbreadHeader) EncodeRLP(_w io.Writer) error {
+ w := rlp.NewEncoderBuffer(_w)
+ _tmp0 := w.List()
+ w.WriteBytes(obj.ParentHash[:])
+ w.WriteBytes(obj.Coinbase[:])
+ w.WriteBytes(obj.Root[:])
+ w.WriteBytes(obj.TxHash[:])
+ w.WriteBytes(obj.ReceiptHash[:])
+ w.WriteBytes(obj.Bloom[:])
+ if obj.Number == nil {
+ w.Write(rlp.EmptyString)
+ } else {
+ if obj.Number.Sign() == -1 {
+ return rlp.ErrNegativeBigInt
+ }
+ w.WriteBigInt(obj.Number)
+ }
+ w.WriteUint64(obj.GasUsed)
+ w.WriteUint64(obj.Time)
+ w.WriteBytes(obj.Extra)
+ w.ListEnd(_tmp0)
+ return w.Flush()
+}
+
+func (obj *BeforeGingerbreadHeader) DecodeRLP(dec *rlp.Stream) error {
+ var _tmp0 BeforeGingerbreadHeader
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // ParentHash:
+ var _tmp1 common.Hash
+ if err := dec.ReadBytes(_tmp1[:]); err != nil {
+ return err
+ }
+ _tmp0.ParentHash = _tmp1
+ // Coinbase:
+ var _tmp2 common.Address
+ if err := dec.ReadBytes(_tmp2[:]); err != nil {
+ return err
+ }
+ _tmp0.Coinbase = _tmp2
+ // Root:
+ var _tmp3 common.Hash
+ if err := dec.ReadBytes(_tmp3[:]); err != nil {
+ return err
+ }
+ _tmp0.Root = _tmp3
+ // TxHash:
+ var _tmp4 common.Hash
+ if err := dec.ReadBytes(_tmp4[:]); err != nil {
+ return err
+ }
+ _tmp0.TxHash = _tmp4
+ // ReceiptHash:
+ var _tmp5 common.Hash
+ if err := dec.ReadBytes(_tmp5[:]); err != nil {
+ return err
+ }
+ _tmp0.ReceiptHash = _tmp5
+ // Bloom:
+ var _tmp6 Bloom
+ if err := dec.ReadBytes(_tmp6[:]); err != nil {
+ return err
+ }
+ _tmp0.Bloom = _tmp6
+ // Number:
+ _tmp7, err := dec.BigInt()
+ if err != nil {
+ return err
+ }
+ _tmp0.Number = _tmp7
+ // GasUsed:
+ _tmp8, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.GasUsed = _tmp8
+ // Time:
+ _tmp9, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp0.Time = _tmp9
+ // Extra:
+ _tmp10, err := dec.Bytes()
+ if err != nil {
+ return err
+ }
+ _tmp0.Extra = _tmp10
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ *obj = _tmp0
+ return nil
+}
diff --git op-geth/core/types/gen_header_rlp.go Celo/core/types/gen_header_rlp.go
deleted file mode 100644
index c79aa8a250230f1ae6fd50316f19a88276dc866e..0000000000000000000000000000000000000000
--- op-geth/core/types/gen_header_rlp.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Code generated by rlpgen. DO NOT EDIT.
-
-package types
-
-import "github.com/ethereum/go-ethereum/rlp"
-import "io"
-
-func (obj *Header) EncodeRLP(_w io.Writer) error {
- w := rlp.NewEncoderBuffer(_w)
- _tmp0 := w.List()
- w.WriteBytes(obj.ParentHash[:])
- w.WriteBytes(obj.UncleHash[:])
- w.WriteBytes(obj.Coinbase[:])
- w.WriteBytes(obj.Root[:])
- w.WriteBytes(obj.TxHash[:])
- w.WriteBytes(obj.ReceiptHash[:])
- w.WriteBytes(obj.Bloom[:])
- if obj.Difficulty == nil {
- w.Write(rlp.EmptyString)
- } else {
- if obj.Difficulty.Sign() == -1 {
- return rlp.ErrNegativeBigInt
- }
- w.WriteBigInt(obj.Difficulty)
- }
- if obj.Number == nil {
- w.Write(rlp.EmptyString)
- } else {
- if obj.Number.Sign() == -1 {
- return rlp.ErrNegativeBigInt
- }
- w.WriteBigInt(obj.Number)
- }
- w.WriteUint64(obj.GasLimit)
- w.WriteUint64(obj.GasUsed)
- w.WriteUint64(obj.Time)
- w.WriteBytes(obj.Extra)
- w.WriteBytes(obj.MixDigest[:])
- w.WriteBytes(obj.Nonce[:])
- _tmp1 := obj.BaseFee != nil
- _tmp2 := obj.WithdrawalsHash != nil
- _tmp3 := obj.BlobGasUsed != nil
- _tmp4 := obj.ExcessBlobGas != nil
- _tmp5 := obj.ParentBeaconRoot != nil
- _tmp6 := obj.RequestsHash != nil
- if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
- if obj.BaseFee == nil {
- w.Write(rlp.EmptyString)
- } else {
- if obj.BaseFee.Sign() == -1 {
- return rlp.ErrNegativeBigInt
- }
- w.WriteBigInt(obj.BaseFee)
- }
- }
- if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
- if obj.WithdrawalsHash == nil {
- w.Write([]byte{0x80})
- } else {
- w.WriteBytes(obj.WithdrawalsHash[:])
- }
- }
- if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
- if obj.BlobGasUsed == nil {
- w.Write([]byte{0x80})
- } else {
- w.WriteUint64((*obj.BlobGasUsed))
- }
- }
- if _tmp4 || _tmp5 || _tmp6 {
- if obj.ExcessBlobGas == nil {
- w.Write([]byte{0x80})
- } else {
- w.WriteUint64((*obj.ExcessBlobGas))
- }
- }
- if _tmp5 || _tmp6 {
- if obj.ParentBeaconRoot == nil {
- w.Write([]byte{0x80})
- } else {
- w.WriteBytes(obj.ParentBeaconRoot[:])
- }
- }
- if _tmp6 {
- if obj.RequestsHash == nil {
- w.Write([]byte{0x80})
- } else {
- w.WriteBytes(obj.RequestsHash[:])
- }
- }
- w.ListEnd(_tmp0)
- return w.Flush()
-}
diff --git op-geth/core/types/gen_receipt_json.go Celo/core/types/gen_receipt_json.go
index 4e544a0b52b61bac03f57c71581117f04280f81e..100e27fd7956c5700f28df0e193d76efa210412b 100644
--- op-geth/core/types/gen_receipt_json.go
+++ Celo/core/types/gen_receipt_json.go
@@ -40,6 +40,7 @@ L1Fee *hexutil.Big `json:"l1Fee,omitempty"`
FeeScalar *big.Float `json:"l1FeeScalar,omitempty"`
L1BaseFeeScalar *hexutil.Uint64 `json:"l1BaseFeeScalar,omitempty"`
L1BlobBaseFeeScalar *hexutil.Uint64 `json:"l1BlobBaseFeeScalar,omitempty"`
+ BaseFee *big.Int `json:"baseFee,omitempty"`
}
var enc Receipt
enc.Type = hexutil.Uint64(r.Type)
@@ -66,6 +67,7 @@ enc.L1Fee = (*hexutil.Big)(r.L1Fee)
enc.FeeScalar = r.FeeScalar
enc.L1BaseFeeScalar = (*hexutil.Uint64)(r.L1BaseFeeScalar)
enc.L1BlobBaseFeeScalar = (*hexutil.Uint64)(r.L1BlobBaseFeeScalar)
+ enc.BaseFee = r.BaseFee
return json.Marshal(&enc)
}
@@ -96,6 +98,7 @@ L1Fee *hexutil.Big `json:"l1Fee,omitempty"`
FeeScalar *big.Float `json:"l1FeeScalar,omitempty"`
L1BaseFeeScalar *hexutil.Uint64 `json:"l1BaseFeeScalar,omitempty"`
L1BlobBaseFeeScalar *hexutil.Uint64 `json:"l1BlobBaseFeeScalar,omitempty"`
+ BaseFee *big.Int `json:"baseFee,omitempty"`
}
var dec Receipt
if err := json.Unmarshal(input, &dec); err != nil {
@@ -177,6 +180,9 @@ r.L1BaseFeeScalar = (*uint64)(dec.L1BaseFeeScalar)
}
if dec.L1BlobBaseFeeScalar != nil {
r.L1BlobBaseFeeScalar = (*uint64)(dec.L1BlobBaseFeeScalar)
+ }
+ if dec.BaseFee != nil {
+ r.BaseFee = dec.BaseFee
}
return nil
}
diff --git op-geth/core/types/rollup_cost.go Celo/core/types/rollup_cost.go
index f398c43af1e460aa4f24e7c464ddeb7d2ab6e2e5..7f02f621e1aacc727b5af771fbbf0c6e6e655250 100644
--- op-geth/core/types/rollup_cost.go
+++ Celo/core/types/rollup_cost.go
@@ -124,6 +124,11 @@ }
forBlock := ^uint64(0)
var cachedFunc l1CostFunc
selectFunc := func(blockTime uint64) l1CostFunc {
+ // Alfajores requires a custom cost function see link for a detailed explanation
+ // https://github.com/celo-org/op-geth/pull/271
+ if config.IsCel2(blockTime) && config.ChainID.Uint64() == params.CeloAlfajoresChainID {
+ return func(rcd RollupCostData) (fee, gasUsed *big.Int) { return new(big.Int), new(big.Int) }
+ }
if !config.IsOptimismEcotone(blockTime) {
return newL1CostFuncBedrock(config, statedb, blockTime)
}
diff --git op-geth/core/vm/celo_errors.go Celo/core/vm/celo_errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..03d230c43358aeb01d00a57a51d863eade9a0e55
--- /dev/null
+++ Celo/core/vm/celo_errors.go
@@ -0,0 +1,9 @@
+package vm
+
+import (
+ "errors"
+)
+
+var (
+ ErrInputLength = errors.New("invalid input length")
+)
diff --git op-geth/core/vm/celo_evm.go Celo/core/vm/celo_evm.go
new file mode 100644
index 0000000000000000000000000000000000000000..93ecb05ceb0756e039319acdd3abcf381d21d8ed
--- /dev/null
+++ Celo/core/vm/celo_evm.go
@@ -0,0 +1,30 @@
+package vm
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/holiman/uint256"
+)
+
+// Call function from ABI and check revert message after call.
+// ABIs can be found at contracts/celo/abigen, e.g. abigen.FeeCurrencyMetaData.GetAbi().
+// args are passed through to the EVM function.
+func (evm *EVM) CallWithABI(contractABI *abi.ABI, funcName string, addr common.Address, gas uint64, args ...interface{}) (leftOverGas uint64, err error) {
+ caller := AccountRef(common.ZeroAddress)
+ input, err := contractABI.Pack(funcName, args...)
+ if err != nil {
+ return 0, fmt.Errorf("pack %s: %w", funcName, err)
+ }
+
+ ret, leftOverGas, err := evm.Call(caller, addr, input, gas, new(uint256.Int))
+ if err != nil {
+ revertReason, err2 := abi.UnpackRevert(ret)
+ if err2 == nil {
+ return 0, fmt.Errorf("%s reverted: %s", funcName, revertReason)
+ }
+ }
+
+ return leftOverGas, err
+}
diff --git op-geth/core/vm/gas_table_test.go Celo/core/vm/gas_table_test.go
index a48fc625dd15643882a8df0800ff1608488690be..867b89a05299275fa77aa755cc2a6b96b37ac0fb 100644
--- op-geth/core/vm/gas_table_test.go
+++ Celo/core/vm/gas_table_test.go
@@ -123,13 +123,13 @@ {"0x61C00060006000f0" + "600052" + "60206000F3", true, 44309, 44309},
// create2(0, 0, 0xc001, 0) without 3860
{"0x600061C00160006000f5" + "600052" + "60206000F3", false, 50471, 50471},
// create2(0, 0, 0xc001, 0) (too large), with 3860
- {"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32012, 100_000},
+ //{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32012, 100_000}, // Disabled due to Celo MaxCodeSize change
// create2(0, 0, 0xc000, 0)
// This case is trying to deploy code at (within) the limit
{"0x600061C00060006000f5" + "600052" + "60206000F3", true, 53528, 53528},
// create2(0, 0, 0xc001, 0)
// This case is trying to deploy code exceeding the limit
- {"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100000},
+ //{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100000}, // Disabled due to Celo MaxCodeSize change
}
func TestCreateGas(t *testing.T) {
diff --git op-geth/eth/api_backend.go Celo/eth/api_backend.go
index bd1b623c367f5e25d66f92bc0c96790bf59e19ca..d03bc146a8f9d31df820bb9f42e3e80931b6772f 100644
--- op-geth/eth/api_backend.go
+++ Celo/eth/api_backend.go
@@ -263,7 +263,8 @@ var context vm.BlockContext
if blockCtx != nil {
context = *blockCtx
} else {
- context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, b.eth.blockchain.Config(), state)
+ feeCurrencyContext := core.GetFeeCurrencyContext(header, b.eth.blockchain.Config(), state)
+ context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, b.eth.blockchain.Config(), state, feeCurrencyContext)
}
return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig)
}
diff --git op-geth/eth/backend.go Celo/eth/backend.go
index 79453719c642055fb1cf5c28865a1f9bb18ba12c..cab5e8ac4834b56ae95f925295a472adb302d3bf 100644
--- op-geth/eth/backend.go
+++ Celo/eth/backend.go
@@ -48,6 +48,7 @@ "github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/internal/celoapi"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/sequencerapi"
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
@@ -245,6 +246,9 @@ }
if config.OverrideOptimismInterop != nil {
overrides.OverrideOptimismInterop = config.OverrideOptimismInterop
}
+ if config.OverrideOptimismCel2 != nil {
+ overrides.OverrideOptimismCel2 = config.OverrideOptimismCel2
+ }
overrides.ApplySuperchainUpgrades = config.ApplySuperchainUpgrades
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, &config.TransactionHistory)
@@ -367,7 +371,8 @@
// APIs return the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else.
func (s *Ethereum) APIs() []rpc.API {
- apis := ethapi.GetAPIs(s.APIBackend)
+ celoBackend := celoapi.NewCeloAPIBackend(s.APIBackend)
+ apis := ethapi.GetAPIs(celoBackend)
// Append any APIs exposed explicitly by the consensus engine
apis = append(apis, s.engine.APIs(s.BlockChain())...)
@@ -396,6 +401,13 @@ Service: NewDebugAPI(s),
}, {
Namespace: "net",
Service: s.netRPCService,
+ },
+ // CELO specific API backend.
+ // For methods in the backend that are already defined (match by name)
+ // on the eth namespace, this will overwrite the original procedures.
+ {
+ Namespace: "eth",
+ Service: celoapi.NewCeloAPI(s, celoBackend),
},
}...)
}
diff --git op-geth/eth/downloader/queue.go Celo/eth/downloader/queue.go
index adad45020040c2fbb7d7dd89c42e55740fe42cdf..53cda3adab1a44d54bf548e6afd21791157f1f85 100644
--- op-geth/eth/downloader/queue.go
+++ Celo/eth/downloader/queue.go
@@ -793,7 +793,8 @@ validate := func(index int, header *types.Header) error {
if txListHashes[index] != header.TxHash {
return errInvalidBody
}
- if uncleListHashes[index] != header.UncleHash {
+ // Pre gingerbread headers do not have a valid uncle hash.
+ if !header.IsPreGingerbread() && uncleListHashes[index] != header.UncleHash {
return errInvalidBody
}
if header.WithdrawalsHash == nil {
diff --git op-geth/eth/ethconfig/config.go Celo/eth/ethconfig/config.go
index 29eca7bb283e461816305242bc20c964a19fbc1a..bb3f485fcca83b9b96aad78d31ae0b0ffbdf25a0 100644
--- op-geth/eth/ethconfig/config.go
+++ Celo/eth/ethconfig/config.go
@@ -159,6 +159,7 @@ // OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"`
OverrideOptimismCanyon *uint64 `toml:",omitempty"`
+ OverrideOptimismCel2 *uint64 `toml:",omitempty"`
OverrideOptimismEcotone *uint64 `toml:",omitempty"`
diff --git op-geth/eth/ethconfig/gen_config.go Celo/eth/ethconfig/gen_config.go
index 72f0e1dc73b552c72b4e005162352a0d290f976d..0b3a996bea321109e45d9f78ab529a39bee784ee 100644
--- op-geth/eth/ethconfig/gen_config.go
+++ Celo/eth/ethconfig/gen_config.go
@@ -53,6 +53,7 @@ RPCTxFeeCap float64
OverrideCancun *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"`
OverrideOptimismCanyon *uint64 `toml:",omitempty"`
+ OverrideOptimismCel2 *uint64 `toml:",omitempty"`
OverrideOptimismEcotone *uint64 `toml:",omitempty"`
OverrideOptimismFjord *uint64 `toml:",omitempty"`
OverrideOptimismGranite *uint64 `toml:",omitempty"`
@@ -107,6 +108,7 @@ enc.RPCTxFeeCap = c.RPCTxFeeCap
enc.OverrideCancun = c.OverrideCancun
enc.OverrideVerkle = c.OverrideVerkle
enc.OverrideOptimismCanyon = c.OverrideOptimismCanyon
+ enc.OverrideOptimismCel2 = c.OverrideOptimismCel2
enc.OverrideOptimismEcotone = c.OverrideOptimismEcotone
enc.OverrideOptimismFjord = c.OverrideOptimismFjord
enc.OverrideOptimismGranite = c.OverrideOptimismGranite
@@ -165,6 +167,7 @@ RPCTxFeeCap *float64
OverrideCancun *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"`
OverrideOptimismCanyon *uint64 `toml:",omitempty"`
+ OverrideOptimismCel2 *uint64 `toml:",omitempty"`
OverrideOptimismEcotone *uint64 `toml:",omitempty"`
OverrideOptimismFjord *uint64 `toml:",omitempty"`
OverrideOptimismGranite *uint64 `toml:",omitempty"`
@@ -293,6 +296,9 @@ c.OverrideVerkle = dec.OverrideVerkle
}
if dec.OverrideOptimismCanyon != nil {
c.OverrideOptimismCanyon = dec.OverrideOptimismCanyon
+ }
+ if dec.OverrideOptimismCel2 != nil {
+ c.OverrideOptimismCel2 = dec.OverrideOptimismCel2
}
if dec.OverrideOptimismEcotone != nil {
c.OverrideOptimismEcotone = dec.OverrideOptimismEcotone
diff --git op-geth/eth/filters/filter.go Celo/eth/filters/filter.go
index 09ccb939073a53e4879fd286553d631411c2301f..a02d42e8d1d169b5bf3fcc8b5f7fa680fa150575 100644
--- op-geth/eth/filters/filter.go
+++ Celo/eth/filters/filter.go
@@ -307,7 +307,13 @@ }
for i, log := range logs {
// Copy log not to modify cache elements
logcopy := *log
- logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash()
+ // Block receipts reference a non existent transaction ocurring after the last transaction.
+ // We use the block hash in place of the transaction hash for the block receipt.
+ if logcopy.TxIndex == uint(len(body.Transactions)) {
+ logcopy.TxHash = logcopy.BlockHash
+ } else {
+ logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash()
+ }
logs[i] = &logcopy
}
return logs, nil
diff --git op-geth/eth/filters/filter_test.go Celo/eth/filters/filter_test.go
index d8b703fee4e97de944de0fe508e24af3b5ad124f..3c2f3f6df519a0aa7c9ca1e9f52ccf5758c34637 100644
--- op-geth/eth/filters/filter_test.go
+++ Celo/eth/filters/filter_test.go
@@ -164,8 +164,8 @@ hash4 = common.BytesToHash([]byte("topic4"))
hash5 = common.BytesToHash([]byte("topic5"))
gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: types.GenesisAlloc{
+ Config: params.TestChainConfigNoCel2,
+ Alloc: core.GenesisAlloc{
addr: {Balance: big.NewInt(0).Mul(big.NewInt(100), big.NewInt(params.Ether))},
contract: {Balance: big.NewInt(0), Code: bytecode},
contract2: {Balance: big.NewInt(0), Code: bytecode},
diff --git op-geth/eth/gasestimator/gasestimator.go Celo/eth/gasestimator/gasestimator.go
index b05f9f200baff91c8122f61eee08b887f232fcf1..28f8dac18bddd286d5d4ba6dafab0d9166f77d1a 100644
--- op-geth/eth/gasestimator/gasestimator.go
+++ Celo/eth/gasestimator/gasestimator.go
@@ -20,10 +20,11 @@ import (
"context"
"errors"
"fmt"
- "math"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@@ -49,7 +50,7 @@
// Estimate returns the lowest possible gas limit that allows the transaction to
// run successfully with the provided context options. It returns an error if the
// transaction would always revert, or if there are unexpected failures.
-func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) {
+func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64, exchangeRates common.ExchangeRates, feeCurrencyBalance *big.Int) (uint64, []byte, error) {
// Binary search the gas limit, as it may need to be higher than the amount used
var (
lo uint64 // lowest-known gas limit where tx execution fails
@@ -71,14 +72,35 @@ feeCap = common.Big0
}
// Recap the highest gas limit with account's available balance.
if feeCap.BitLen() != 0 {
- balance := opts.State.GetBalance(call.From).ToBig()
-
- available := balance
+ celoBalance := opts.State.GetBalance(call.From).ToBig()
+ available := celoBalance
+ if call.FeeCurrency != nil {
+ if !call.IsFeeCurrencyDenominated() {
+ // CIP-66, prices are given in native token.
+ // We need to check the allowance in the converted feeCurrency
+ var err error
+ feeCap, err = exchange.ConvertCeloToCurrency(exchangeRates, call.FeeCurrency, feeCap)
+ if err != nil {
+ return 0, nil, err
+ }
+ }
+ available = feeCurrencyBalance
+ }
if call.Value != nil {
- if call.Value.Cmp(available) >= 0 {
+ if call.Value.Cmp(celoBalance) > 0 {
return 0, nil, core.ErrInsufficientFundsForTransfer
}
- available.Sub(available, call.Value)
+ if call.FeeCurrency == nil {
+ available.Sub(available, call.Value)
+ if available.Cmp(big.NewInt(0)) <= 0 {
+ return 0, nil, core.ErrInsufficientFundsForTransfer
+ }
+ }
+ }
+
+ // cap the available by the maxFeeInFeeCurrency
+ if call.MaxFeeInFeeCurrency != nil {
+ available = math.BigMin(available, call.MaxFeeInFeeCurrency)
}
if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
@@ -98,8 +120,10 @@ transfer := call.Value
if transfer == nil {
transfer = new(big.Int)
}
- log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
- "sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
+ log.Debug("Gas estimation capped by limited funds", "original", hi, "celo balance", celoBalance,
+ "feeCurrency balance", feeCurrencyBalance, "sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance,
+ "feeCurrency", call.FeeCurrency, "maxFeeInFeeCurrency", call.MaxFeeInFeeCurrency,
+ )
hi = allowance.Uint64()
}
}
@@ -217,8 +241,9 @@ // call invocation.
func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) {
// Assemble the call and the call context
var (
- msgContext = core.NewEVMTxContext(call)
- evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, opts.Config, opts.State)
+ feeCurrencyContext = core.GetFeeCurrencyContext(opts.Header, opts.Config, opts.State)
+ msgContext = core.NewEVMTxContext(call)
+ evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, opts.Config, opts.State, feeCurrencyContext)
dirtyState = opts.State.Copy()
)
diff --git op-geth/eth/gasprice/gasprice_test.go Celo/eth/gasprice/gasprice_test.go
index fdba2e584b42c70188a8a194b60348cf59c0b354..8e2e82bee59a5fd1a150b2edcdfdc3d7509658d7 100644
--- op-geth/eth/gasprice/gasprice_test.go
+++ Celo/eth/gasprice/gasprice_test.go
@@ -146,6 +146,7 @@ emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob)
emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
)
config.LondonBlock = londonBlock
+ config.GingerbreadBlock = londonBlock
config.ArrowGlacierBlock = londonBlock
config.GrayGlacierBlock = londonBlock
var engine consensus.Engine = beacon.New(ethash.NewFaker())
diff --git op-geth/eth/handler_test.go Celo/eth/handler_test.go
index 7b250df2e915c16c435ade719311ac0aacd48c1d..aa52f43ced3d0b1b5215a497bb6577b73d5030fa 100644
--- op-geth/eth/handler_test.go
+++ Celo/eth/handler_test.go
@@ -108,13 +108,14 @@ pending := make(map[common.Address][]*txpool.LazyTransaction)
for addr, batch := range batches {
for _, tx := range batch {
pending[addr] = append(pending[addr], &txpool.LazyTransaction{
- Hash: tx.Hash(),
- Tx: tx,
- Time: tx.Time(),
- GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
- GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
- Gas: tx.Gas(),
- BlobGas: tx.BlobGas(),
+ Hash: tx.Hash(),
+ Tx: tx,
+ Time: tx.Time(),
+ GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
+ GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
+ Gas: tx.Gas(),
+ BlobGas: tx.BlobGas(),
+ FeeCurrency: tx.FeeCurrency(),
})
}
}
diff --git op-geth/eth/interop.go Celo/eth/interop.go
index f044f20120b40939518f3a8c12b37031a570b7a1..5e7cef4ad6aae85cb3316ea42e756b19191605e7 100644
--- op-geth/eth/interop.go
+++ Celo/eth/interop.go
@@ -5,6 +5,7 @@ "context"
"errors"
"fmt"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/interoptypes"
@@ -37,12 +38,12 @@ return nil, fmt.Errorf("state %s (block %d) is unavailable for log simulation: %w", header.Root, header.Number.Uint64(), err)
}
var vmConf vm.Config
signer := types.MakeSigner(chainConfig, header.Number, header.Time)
- message, err := core.TransactionToMessage(tx, signer, header.BaseFee)
+ chainCtx := ethapi.NewChainContext(context.Background(), s.APIBackend)
+ blockCtx := core.NewEVMBlockContext(header, chainCtx, &header.Coinbase, chainConfig, state, &common.FeeCurrencyContext{})
+ message, err := core.TransactionToMessage(tx, signer, header.BaseFee, blockCtx.FeeCurrencyContext.ExchangeRates)
if err != nil {
return nil, fmt.Errorf("cannot convert tx to message for log simulation: %w", err)
}
- chainCtx := ethapi.NewChainContext(context.Background(), s.APIBackend)
- blockCtx := core.NewEVMBlockContext(header, chainCtx, &header.Coinbase, chainConfig, state)
txCtx := core.NewEVMTxContext(message)
vmenv := vm.NewEVM(blockCtx, txCtx, state, chainConfig, vmConf)
state.SetTxContext(tx.Hash(), 0)
diff --git op-geth/eth/state_accessor.go Celo/eth/state_accessor.go
index 5941e0a15c46a807abba293eec365e8eb0a91b29..90a0b1c1c8e7226589d36d8e665bc44044310ef5 100644
--- op-geth/eth/state_accessor.go
+++ Celo/eth/state_accessor.go
@@ -234,15 +234,16 @@ statedb, release, err := eth.stateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, vm.BlockContext{}, nil, nil, err
}
+ feeCurrencyContext := core.GetFeeCurrencyContext(block.Header(), eth.blockchain.Config(), statedb)
// Insert parent beacon block root in the state as per EIP-4788.
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
- context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb)
+ context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb, feeCurrencyContext)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
}
// If prague hardfork, insert parent block hash in the state as per EIP-2935.
if eth.blockchain.Config().IsPrague(block.Number(), block.Time()) {
- context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb)
+ context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb, feeCurrencyContext)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
core.ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
}
@@ -252,10 +253,10 @@ }
// Recompute transactions up to the target index.
signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time())
for idx, tx := range block.Transactions() {
+ context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb, feeCurrencyContext)
// Assemble the transaction call message and return if the requested offset
- msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
+ msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), context.FeeCurrencyContext.ExchangeRates)
txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb)
if idx == txIndex {
return tx, context, statedb, release, nil
}
diff --git op-geth/eth/tracers/api.go Celo/eth/tracers/api.go
index d77b4886980e586092e9d0d65a3de85e6c0366c3..e8dddf7a70bb8e64a3f6e85b3412f074423f6776 100644
--- op-geth/eth/tracers/api.go
+++ Celo/eth/tracers/api.go
@@ -266,12 +266,13 @@
// Fetch and execute the block trace taskCh
for task := range taskCh {
var (
- signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
- blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), task.statedb)
+ feeCurrencyContext = core.GetFeeCurrencyContext(end.Header(), api.backend.ChainConfig(), task.statedb)
+ signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
+ blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), task.statedb, feeCurrencyContext)
)
// Trace all the transactions contained within
for i, tx := range task.block.Transactions() {
- msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee())
+ msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee(), feeCurrencyContext.ExchangeRates)
txctx := &Context{
BlockHash: task.block.Hash(),
BlockNumber: task.block.Number(),
@@ -376,16 +377,17 @@ if err != nil {
failed = err
break
}
+ feeCurrencyContext := core.GetFeeCurrencyContext(end.Header(), api.backend.ChainConfig(), statedb)
// Insert block's parent beacon block root in the state
// as per EIP-4788.
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
- context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb)
+ context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb, feeCurrencyContext)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
}
// Insert parent hash in history contract.
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
- context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb)
+ context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb, feeCurrencyContext)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
core.ProcessParentBlockHash(next.ParentHash(), vmenv, statedb)
}
@@ -563,7 +565,8 @@ var (
roots []common.Hash
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig()
- vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, chainConfig, statedb)
+ feeCurrencyContext = core.GetFeeCurrencyContext(block.Header(), chainConfig, statedb)
+ vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, chainConfig, statedb, feeCurrencyContext)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
@@ -578,7 +581,7 @@ if err := ctx.Err(); err != nil {
return nil, err
}
var (
- msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
+ msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee(), vmctx.FeeCurrencyContext.ExchangeRates)
txContext = core.NewEVMTxContext(msg)
vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{})
)
@@ -642,11 +645,12 @@ }
}
// Native tracers have low overhead
var (
- txs = block.Transactions()
- blockHash = block.Hash()
- blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb)
- signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
- results = make([]*txTraceResult, len(txs))
+ txs = block.Transactions()
+ blockHash = block.Hash()
+ feeCurrencyContext = core.GetFeeCurrencyContext(block.Header(), api.backend.ChainConfig(), statedb)
+ blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb, feeCurrencyContext)
+ signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
+ results = make([]*txTraceResult, len(txs))
)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
@@ -658,7 +662,7 @@ core.ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
}
for i, tx := range txs {
// Generate the next state snapshot fast without tracing
- msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
+ msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), feeCurrencyContext.ExchangeRates)
txctx := &Context{
BlockHash: blockHash,
BlockNumber: block.Number(),
@@ -690,6 +694,8 @@ threads := runtime.NumCPU()
if threads > len(txs) {
threads = len(txs)
}
+ exchangeRates := core.GetExchangeRates(block.Header(), api.backend.ChainConfig(), statedb)
+ feeCurrencyContext := core.GetFeeCurrencyContext(block.Header(), api.backend.ChainConfig(), statedb)
jobs := make(chan *txTraceTask, threads)
for th := 0; th < threads; th++ {
pend.Add(1)
@@ -697,7 +703,7 @@ go func() {
defer pend.Done()
// Fetch and execute the next transaction trace tasks
for task := range jobs {
- msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee())
+ msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee(), exchangeRates)
txctx := &Context{
BlockHash: blockHash,
BlockNumber: block.Number(),
@@ -708,7 +714,7 @@ // Reconstruct the block context for each transaction
// as the GetHash function of BlockContext is not safe for
// concurrent use.
// See: https://github.com/ethereum/go-ethereum/issues/29114
- blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), task.statedb)
+ blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), task.statedb, feeCurrencyContext)
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
@@ -721,7 +727,7 @@ }
// Feed the transactions into the tracers and return
var failed error
- blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb)
+ blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb, feeCurrencyContext)
txloop:
for i, tx := range txs {
// Send the trace task over for execution
@@ -734,7 +740,7 @@ case jobs <- task:
}
// Generate the next state snapshot fast without tracing
- msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
+ msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), blockCtx.FeeCurrencyContext.ExchangeRates)
statedb.SetTxContext(tx.Hash(), i)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
@@ -795,11 +801,12 @@ logConfig.Debug = true
// Execute transaction, either tracing all or just the requested one
var (
- dumps []string
- signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
- chainConfig = api.backend.ChainConfig()
- vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, chainConfig, statedb)
- canon = true
+ dumps []string
+ signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
+ chainConfig = api.backend.ChainConfig()
+ feeCurrencyContext = core.GetFeeCurrencyContext(block.Header(), chainConfig, statedb)
+ vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, chainConfig, statedb, feeCurrencyContext)
+ canon = true
)
// Check if there are any overrides: the caller may wish to enable a future
// fork when executing this block. Note, such overrides are only applicable to the
@@ -821,7 +828,7 @@ }
for i, tx := range block.Transactions() {
// Prepare the transaction for un-traced execution
var (
- msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
+ msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee(), vmctx.FeeCurrencyContext.ExchangeRates)
txContext = core.NewEVMTxContext(msg)
vmConf vm.Config
dump *os.File
@@ -928,7 +935,8 @@ if err != nil {
return nil, err
}
defer release()
- msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()), block.BaseFee())
+ exchangeRates := core.GetExchangeRates(block.Header(), api.backend.ChainConfig(), statedb)
+ msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()), block.BaseFee(), exchangeRates)
if err != nil {
return nil, err
}
@@ -977,7 +985,15 @@ return nil, err
}
if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) {
- return nil, errors.New("l2geth does not have a debug_traceCall method")
+ if api.backend.HistoricalRPCService() != nil {
+ var histResult json.RawMessage
+ err := api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceCall", args, blockNrOrHash, config)
+ if err != nil {
+ return nil, fmt.Errorf("historical backend error: %w", err)
+ }
+ return histResult, nil
+ }
+ return nil, rpc.ErrNoHistoricalFallback
}
// try to recompute the state
@@ -996,7 +1012,8 @@ return nil, err
}
defer release()
- vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb)
+ feeCurrencyContext := core.GetFeeCurrencyContext(block.Header(), api.backend.ChainConfig(), statedb)
+ vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb, feeCurrencyContext)
// Apply the customization rules if required.
if config != nil {
config.BlockOverrides.Apply(&vmctx)
@@ -1012,7 +1029,7 @@ if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
return nil, err
}
var (
- msg = args.ToMessage(vmctx.BaseFee, true, true)
+ msg = args.ToMessage(vmctx.BaseFee, true, true, feeCurrencyContext.ExchangeRates)
tx = args.ToTransaction(types.LegacyTxType)
traceConfig *TraceConfig
)
diff --git op-geth/eth/tracers/api_test.go Celo/eth/tracers/api_test.go
index 2ea4f196f0f77966cf549cdc2a01526dddd4da02..a1661cf4ba97f162e446a5f4f27e7fd6015c3494 100644
--- op-geth/eth/tracers/api_test.go
+++ Celo/eth/tracers/api_test.go
@@ -246,12 +246,13 @@ }
if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, vm.BlockContext{}, statedb, release, nil
}
+ feeCurrencyContext := core.GetFeeCurrencyContext(block.Header(), b.chainConfig, statedb)
// Recompute transactions up to the target index.
signer := types.MakeSigner(b.chainConfig, block.Number(), block.Time())
+ context := core.NewEVMBlockContext(block.Header(), b.chain, nil, b.chainConfig, statedb, feeCurrencyContext)
for idx, tx := range block.Transactions() {
- msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
+ msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), context.FeeCurrencyContext.ExchangeRates)
txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(block.Header(), b.chain, nil, b.chainConfig, statedb)
if idx == txIndex {
return tx, context, statedb, release, nil
}
diff --git op-geth/eth/tracers/internal/tracetest/calltrace_test.go Celo/eth/tracers/internal/tracetest/calltrace_test.go
index e97023825eb780c6c85b0f7a51f63fe0f9a2ba30..af8017b0c05a9ab4019cf2759bf26fca8174a64d 100644
--- op-geth/eth/tracers/internal/tracetest/calltrace_test.go
+++ Celo/eth/tracers/internal/tracetest/calltrace_test.go
@@ -127,7 +127,7 @@ if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
state.StateDB.SetLogger(tracer.Hooks)
- msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
+ msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
@@ -219,7 +219,7 @@ Time: uint64(test.Context.Time),
Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit),
}
- msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
+ msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
}
@@ -376,7 +376,7 @@ Origin: origin,
GasPrice: tx.GasPrice(),
}
evm := vm.NewEVM(context, txContext, state.StateDB, config, vm.Config{Tracer: tc.tracer.Hooks})
- msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0))
+ msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0), nil)
if err != nil {
t.Fatalf("test %v: failed to create message: %v", tc.name, err)
}
diff --git op-geth/eth/tracers/internal/tracetest/flat_calltrace_test.go Celo/eth/tracers/internal/tracetest/flat_calltrace_test.go
index 050950c8643bb0d81776a7b6af630886e299b16f..125b85fa096b1f73fc0bf9918b684587f64af2c1 100644
--- op-geth/eth/tracers/internal/tracetest/flat_calltrace_test.go
+++ Celo/eth/tracers/internal/tracetest/flat_calltrace_test.go
@@ -94,7 +94,7 @@ return fmt.Errorf("failed to create call tracer: %v", err)
}
state.StateDB.SetLogger(tracer.Hooks)
- msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
+ msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates)
if err != nil {
return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
}
diff --git op-geth/eth/tracers/internal/tracetest/prestate_test.go Celo/eth/tracers/internal/tracetest/prestate_test.go
index 9cbd12669489fdab0c392ece606008d89ca37c66..5824adb4eacacd6a274d037e36c596251634f367 100644
--- op-geth/eth/tracers/internal/tracetest/prestate_test.go
+++ Celo/eth/tracers/internal/tracetest/prestate_test.go
@@ -104,7 +104,7 @@ t.Fatalf("failed to create call tracer: %v", err)
}
state.StateDB.SetLogger(tracer.Hooks)
- msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
+ msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
diff --git op-geth/eth/tracers/internal/tracetest/util.go Celo/eth/tracers/internal/tracetest/util.go
index a74a96f8a48961ce39b67868478e84dd990e28d7..fd9483f263f21ac340621a46b6079a9927a75642 100644
--- op-geth/eth/tracers/internal/tracetest/util.go
+++ Celo/eth/tracers/internal/tracetest/util.go
@@ -32,6 +32,8 @@ Time math.HexOrDecimal64 `json:"timestamp"`
GasLimit math.HexOrDecimal64 `json:"gasLimit"`
Miner common.Address `json:"miner"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
+ // Celo specific
+ FeeCurrencyContext common.FeeCurrencyContext `json:"feeCurrencyContext"`
}
func (c *callContext) toBlockContext(genesis *core.Genesis) vm.BlockContext {
@@ -43,6 +45,8 @@ BlockNumber: new(big.Int).SetUint64(uint64(c.Number)),
Time: uint64(c.Time),
Difficulty: (*big.Int)(c.Difficulty),
GasLimit: uint64(c.GasLimit),
+ // Celo specific
+ FeeCurrencyContext: c.FeeCurrencyContext,
}
if genesis.Config.IsLondon(context.BlockNumber) {
context.BaseFee = (*big.Int)(c.BaseFee)
diff --git op-geth/eth/tracers/native/prestate.go Celo/eth/tracers/native/prestate.go
index b353c069606721029b70065fbb5c81394b9763da..ebef3387df5b2d09e9dea5e76045c2c5401b345f 100644
--- op-geth/eth/tracers/native/prestate.go
+++ Celo/eth/tracers/native/prestate.go
@@ -93,6 +93,8 @@ Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnOpcode: t.OnOpcode,
+ // Celo
+ TraceDebitCredit: true,
},
GetResult: t.GetResult,
Stop: t.Stop,
@@ -158,6 +160,9 @@
t.lookupAccount(from)
t.lookupAccount(t.to)
t.lookupAccount(env.Coinbase)
+ if tx.FeeCurrency() != nil {
+ t.lookupAccount(*tx.FeeCurrency())
+ }
}
func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) {
diff --git op-geth/eth/tracers/tracers_test.go Celo/eth/tracers/tracers_test.go
index 3cce7bffa19aed0a3e6cf9203d0b8d3396c97539..4204136deb79a5fb1c5f5b2875de42f42d45ae4f 100644
--- op-geth/eth/tracers/tracers_test.go
+++ Celo/eth/tracers/tracers_test.go
@@ -90,7 +90,7 @@ //EnableMemory: false,
//EnableReturnData: false,
})
evm := vm.NewEVM(context, txContext, state.StateDB, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer.Hooks()})
- msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
+ msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
}
diff --git op-geth/ethclient/ethclient.go Celo/ethclient/ethclient.go
index a1148bcedbc6e040c2275d2336b09c90edc8be48..75d3ebeef79a0d4788c0d0c50357e4f619655d05 100644
--- op-geth/ethclient/ethclient.go
+++ Celo/ethclient/ethclient.go
@@ -151,7 +151,10 @@ // Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles")
}
- if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
+ // In celo before the ginerbread hardfork, blocks had no uncles hash and no
+ // uncles, so in those cases it is legitimate to have an empty uncles hash.
+ var emptyHash common.Hash
+ if head.UncleHash != emptyHash && head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
return nil, errors.New("server returned empty uncle list but block header indicates uncles")
}
if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 {
@@ -564,11 +567,31 @@ }
return (*big.Int)(&hex), nil
}
+// SuggestGasPriceForCurrency retrieves the currently suggested gas price to allow a timely
+// execution of a transaction in the given fee currency.
+func (ec *Client) SuggestGasPriceForCurrency(ctx context.Context, feeCurrency *common.Address) (*big.Int, error) {
+ var hex hexutil.Big
+ if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice", feeCurrency); err != nil {
+ return nil, err
+ }
+ return (*big.Int)(&hex), nil
+}
+
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to
// allow a timely execution of a transaction.
func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
var hex hexutil.Big
if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas"); err != nil {
+ return nil, err
+ }
+ return (*big.Int)(&hex), nil
+}
+
+// SuggestGasTipCapForCurrency retrieves the currently suggested gas tip cap after 1559 to
+// allow a timely execution of a transaction in the given fee currency.
+func (ec *Client) SuggestGasTipCapForCurrency(ctx context.Context, feeCurrency *common.Address) (*big.Int, error) {
+ var hex hexutil.Big
+ if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas", feeCurrency); err != nil {
return nil, err
}
return (*big.Int)(&hex), nil
diff --git op-geth/funding.json Celo/funding.json
new file mode 100644
index 0000000000000000000000000000000000000000..a6b4c73180af2b2b92704b87bb978936a832cbbe
--- /dev/null
+++ Celo/funding.json
@@ -0,0 +1,5 @@
+{
+ "opRetro": {
+ "projectId": "0x839f24397fbcd261408f074eaf35aee98f500f5185a27e6c470c5307e967c017"
+ }
+}
diff --git op-geth/graphql/graphql.go Celo/graphql/graphql.go
index 65c36d4fb49a946d3d383c88bec2568c595cdee8..8fc0dfd4edb4a212a8006b52d9e5135de71cec69 100644
--- op-geth/graphql/graphql.go
+++ Celo/graphql/graphql.go
@@ -1270,7 +1270,7 @@ }
// Resolver is the top-level object in the GraphQL hierarchy.
type Resolver struct {
- backend ethapi.Backend
+ backend ethapi.CeloBackend
filterSystem *filters.FilterSystem
}
diff --git op-geth/graphql/graphql_test.go Celo/graphql/graphql_test.go
index a6be589a0f1268036cec000c440921a6cf1ecc5c..f15cb0992cf0cf17a7fc0cbc820ebb536ee42477 100644
--- op-geth/graphql/graphql_test.go
+++ Celo/graphql/graphql_test.go
@@ -39,6 +39,7 @@ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/filters"
+ "github.com/ethereum/go-ethereum/internal/celoapi"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
@@ -480,7 +481,8 @@ t.Fatalf("could not create import blocks: %v", err)
}
// Set up handler
filterSystem := filters.NewFilterSystem(ethBackend.APIBackend, filters.Config{})
- handler, err := newHandler(stack, ethBackend.APIBackend, filterSystem, []string{}, []string{})
+ celoBackend := celoapi.NewCeloAPIBackend(ethBackend.APIBackend)
+ handler, err := newHandler(stack, celoBackend, filterSystem, []string{}, []string{})
if err != nil {
t.Fatalf("could not create graphql service: %v", err)
}
diff --git op-geth/graphql/service.go Celo/graphql/service.go
index 584165bdb802e06c09a7994e8349be4b5c8f70f5..43271537e1d05c5c01a144be86886f4bfeaf1341 100644
--- op-geth/graphql/service.go
+++ Celo/graphql/service.go
@@ -25,6 +25,7 @@ "sync"
"time"
"github.com/ethereum/go-ethereum/eth/filters"
+ "github.com/ethereum/go-ethereum/internal/celoapi"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
@@ -107,13 +108,14 @@ }
// New constructs a new GraphQL service instance.
func New(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cors, vhosts []string) error {
- _, err := newHandler(stack, backend, filterSystem, cors, vhosts)
+ celoBackend := celoapi.NewCeloAPIBackend(backend)
+ _, err := newHandler(stack, celoBackend, filterSystem, cors, vhosts)
return err
}
// newHandler returns a new `http.Handler` that will answer GraphQL queries.
// It additionally exports an interactive query browser on the / endpoint.
-func newHandler(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cors, vhosts []string) (*handler, error) {
+func newHandler(stack *node.Node, backend ethapi.CeloBackend, filterSystem *filters.FilterSystem, cors, vhosts []string) (*handler, error) {
q := Resolver{backend, filterSystem}
s, err := graphql.ParseSchema(schema, &q)
diff --git op-geth/internal/celoapi/api.go Celo/internal/celoapi/api.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ce71e82e3f649b17b518a3c764badd8770f520f
--- /dev/null
+++ Celo/internal/celoapi/api.go
@@ -0,0 +1,97 @@
+package celoapi
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/internal/ethapi"
+)
+
+type Ethereum interface {
+ BlockChain() *core.BlockChain
+}
+
+type CeloAPI struct {
+ ethAPI *ethapi.EthereumAPI
+ eth Ethereum
+}
+
+func NewCeloAPI(e Ethereum, b ethapi.CeloBackend) *CeloAPI {
+ return &CeloAPI{
+ ethAPI: ethapi.NewEthereumAPI(b),
+ eth: e,
+ }
+}
+
+func (c *CeloAPI) convertedCurrencyValue(v *hexutil.Big, feeCurrency *common.Address) (*hexutil.Big, error) {
+ if feeCurrency != nil {
+ convertedTipCap, err := c.convertCeloToCurrency(v.ToInt(), feeCurrency)
+ if err != nil {
+ return nil, fmt.Errorf("convert to feeCurrency: %w", err)
+ }
+ v = (*hexutil.Big)(convertedTipCap)
+ }
+ return v, nil
+}
+
+func (c *CeloAPI) celoBackendCurrentState() (*contracts.CeloBackend, error) {
+ state, err := c.eth.BlockChain().State()
+ if err != nil {
+ return nil, fmt.Errorf("retrieve HEAD blockchain state': %w", err)
+ }
+
+ cb := &contracts.CeloBackend{
+ ChainConfig: c.eth.BlockChain().Config(),
+ State: state,
+ }
+ return cb, nil
+}
+
+func (c *CeloAPI) convertCeloToCurrency(nativePrice *big.Int, feeCurrency *common.Address) (*big.Int, error) {
+ cb, err := c.celoBackendCurrentState()
+ if err != nil {
+ return nil, err
+ }
+ exchangeRates, err := contracts.GetExchangeRates(cb)
+ if err != nil {
+ return nil, fmt.Errorf("retrieve exchange rates from current state: %w", err)
+ }
+ return exchange.ConvertCeloToCurrency(exchangeRates, feeCurrency, nativePrice)
+}
+
+// GasPrice wraps the original JSON RPC `eth_gasPrice` and adds an additional
+// optional parameter `feeCurrency` for fee-currency conversion.
+// When `feeCurrency` is not given, then the original JSON RPC method is called without conversion.
+func (c *CeloAPI) GasPrice(ctx context.Context, feeCurrency *common.Address) (*hexutil.Big, error) {
+ tipcap, err := c.ethAPI.GasPrice(ctx)
+ if err != nil {
+ return nil, err
+ }
+ // Between the call to `ethapi.GasPrice` and the call to fetch and convert the rates,
+ // there is a chance of a state-change. This means that gas-price suggestion is calculated
+ // based on state of block x, while the currency conversion could be calculated based on block
+ // x+1.
+ // However, a similar race condition is present in the `ethapi.GasPrice` method itself.
+ return c.convertedCurrencyValue(tipcap, feeCurrency)
+}
+
+// MaxPriorityFeePerGas wraps the original JSON RPC `eth_maxPriorityFeePerGas` and adds an additional
+// optional parameter `feeCurrency` for fee-currency conversion.
+// When `feeCurrency` is not given, then the original JSON RPC method is called without conversion.
+func (c *CeloAPI) MaxPriorityFeePerGas(ctx context.Context, feeCurrency *common.Address) (*hexutil.Big, error) {
+ tipcap, err := c.ethAPI.MaxPriorityFeePerGas(ctx)
+ if err != nil {
+ return nil, err
+ }
+ // Between the call to `ethapi.MaxPriorityFeePerGas` and the call to fetch and convert the rates,
+ // there is a chance of a state-change. This means that gas-price suggestion is calculated
+ // based on state of block x, while the currency conversion could be calculated based on block
+ // x+1.
+ return c.convertedCurrencyValue(tipcap, feeCurrency)
+}
diff --git op-geth/internal/celoapi/backend.go Celo/internal/celoapi/backend.go
new file mode 100644
index 0000000000000000000000000000000000000000..6125df6865515b29e78f07f0b7e3e4c27d6b7c80
--- /dev/null
+++ Celo/internal/celoapi/backend.go
@@ -0,0 +1,77 @@
+package celoapi
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
+ "github.com/ethereum/go-ethereum/contracts"
+ "github.com/ethereum/go-ethereum/internal/ethapi"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+func NewCeloAPIBackend(b ethapi.Backend) *CeloAPIBackend {
+ return &CeloAPIBackend{
+ Backend: b,
+ }
+}
+
+// CeloAPIBackend is a wrapper for the ethapi.Backend, that provides additional Celo specific
+// functionality. CeloAPIBackend is mainly passed to the JSON RPC services and provides
+// an easy way to make extra functionality available in the service internal methods without
+// having to change their call signature significantly.
+type CeloAPIBackend struct {
+ ethapi.Backend
+}
+
+func (b *CeloAPIBackend) getContractCaller(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash) (*contracts.CeloBackend, error) {
+ state, _, err := b.Backend.StateAndHeaderByNumberOrHash(
+ ctx,
+ blockNumOrHash,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("retrieve state for block hash %s: %w", blockNumOrHash.String(), err)
+ }
+ return &contracts.CeloBackend{
+ ChainConfig: b.Backend.ChainConfig(),
+ State: state,
+ }, nil
+}
+
+func (b *CeloAPIBackend) GetFeeBalance(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, account common.Address, feeCurrency *common.Address) (*big.Int, error) {
+ cb, err := b.getContractCaller(ctx, blockNumOrHash)
+ if err != nil {
+ return nil, err
+ }
+ return contracts.GetFeeBalance(cb, account, feeCurrency), nil
+}
+
+func (b *CeloAPIBackend) GetExchangeRates(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error) {
+ contractBackend, err := b.getContractCaller(ctx, blockNumOrHash)
+ if err != nil {
+ return nil, err
+ }
+ exchangeRates, err := contracts.GetExchangeRates(contractBackend)
+ if err != nil {
+ return nil, err
+ }
+ return exchangeRates, nil
+}
+
+func (b *CeloAPIBackend) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, celoAmount *big.Int, toFeeCurrency *common.Address) (*big.Int, error) {
+ er, err := b.GetExchangeRates(ctx, blockNumOrHash)
+ if err != nil {
+ return nil, err
+ }
+ return exchange.ConvertCeloToCurrency(er, toFeeCurrency, celoAmount)
+}
+
+func (b *CeloAPIBackend) ConvertToCelo(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, currencyAmount *big.Int, fromFeeCurrency *common.Address) (*big.Int, error) {
+ er, err := b.GetExchangeRates(ctx, blockNumOrHash)
+ if err != nil {
+ return nil, err
+ }
+ return exchange.ConvertCurrencyToCelo(er, fromFeeCurrency, currencyAmount)
+}
diff --git op-geth/internal/ethapi/api.go Celo/internal/ethapi/api.go
index 7712d71273f3c2521f8f5944fbf58ecf6dc08cc1..69f4fe16c91bf17abf4b90e1b122191525589d7d 100644
--- op-geth/internal/ethapi/api.go
+++ Celo/internal/ethapi/api.go
@@ -59,6 +59,7 @@ // allowed to produce in order to speed up calculations.
const estimateGasErrorRatio = 0.015
var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
+var emptyExchangeRates = make(common.ExchangeRates)
// EthereumAPI provides an API to access Ethereum related information.
type EthereumAPI struct {
@@ -304,11 +305,11 @@ // passwords and are therefore considered private by default.
type PersonalAccountAPI struct {
am *accounts.Manager
nonceLock *AddrLocker
- b Backend
+ b CeloBackend
}
// NewPersonalAccountAPI creates a new PersonalAccountAPI.
-func NewPersonalAccountAPI(b Backend, nonceLock *AddrLocker) *PersonalAccountAPI {
+func NewPersonalAccountAPI(b CeloBackend, nonceLock *AddrLocker) *PersonalAccountAPI {
return &PersonalAccountAPI{
am: b.AccountManager(),
nonceLock: nonceLock,
@@ -638,11 +639,11 @@ }
// BlockChainAPI provides an API to access Ethereum blockchain data.
type BlockChainAPI struct {
- b Backend
+ b CeloBackend
}
// NewBlockChainAPI creates a new Ethereum blockchain API.
-func NewBlockChainAPI(b Backend) *BlockChainAPI {
+func NewBlockChainAPI(b CeloBackend) *BlockChainAPI {
return &BlockChainAPI{b}
}
@@ -1032,7 +1033,8 @@ if err != nil {
return nil, err
}
txs := block.Transactions()
- if len(txs) != len(receipts) {
+ // Legacy Celo blocks sometimes include an extra block receipt. See https://docs.celo.org/developer/migrate/from-ethereum#core-contract-calls
+ if len(txs) != len(receipts) && len(txs)+1 != len(receipts) {
return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts))
}
@@ -1041,9 +1043,12 @@ signer := types.MakeSigner(api.b.ChainConfig(), block.Number(), block.Time())
result := make([]map[string]interface{}, len(receipts))
for i, receipt := range receipts {
- result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i, api.b.ChainConfig())
+ if i == len(txs) {
+ result[i] = marshalBlockReceipt(receipt, block.Hash(), block.NumberU64(), i)
+ } else {
+ result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i, api.b.ChainConfig())
+ }
}
-
return result, nil
}
@@ -1245,7 +1250,8 @@ return header
}
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
- blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, b.ChainConfig(), state)
+ feeCurrencyContext := core.GetFeeCurrencyContext(header, b.ChainConfig(), state)
+ blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, b.ChainConfig(), state, feeCurrencyContext)
if blockOverrides != nil {
blockOverrides.Apply(&blockCtx)
}
@@ -1280,7 +1286,7 @@ // Get a new instance of the EVM.
if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil {
return nil, err
}
- msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks)
+ msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks, blockContext.FeeCurrencyContext.ExchangeRates)
// Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap).
if msg.GasPrice.Sign() == 0 {
@@ -1321,7 +1327,7 @@ }
return result, nil
}
-func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
+func DoCall(ctx context.Context, b CeloBackend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
@@ -1415,7 +1421,7 @@ // DoEstimateGas returns the lowest possible gas limit that allows the transaction to run
// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if
// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &
// non-zero) and `gasCap` (if non-zero).
-func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) {
+func DoEstimateGas(ctx context.Context, b CeloBackend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) {
// Retrieve the base state and mutate it with any overrides
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
@@ -1440,10 +1446,31 @@ }
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
return 0, err
}
- call := args.ToMessage(header.BaseFee, true, true)
+
+ // Celo specific: get exchange rates if fee currency is specified
+ exchangeRates := emptyExchangeRates
+ if args.FeeCurrency != nil {
+ // It is debatable whether we should use the block itself or the parent block here.
+ // Usually, user would probably like the recent rates after the block, so we use the block itself.
+ exchangeRates, err = b.GetExchangeRates(ctx, blockNrOrHash)
+ if err != nil {
+ return 0, fmt.Errorf("get exchange rates for block: %v err: %w", header.Hash(), err)
+ }
+ }
+
+ call := args.ToMessage(header.BaseFee, true, true, exchangeRates)
+
+ // Celo specific: get balance of fee currency if fee currency is specified
+ feeCurrencyBalance := new(big.Int)
+ if args.FeeCurrency != nil {
+ feeCurrencyBalance, err = b.GetFeeBalance(ctx, blockNrOrHash, call.From, args.FeeCurrency)
+ if err != nil {
+ return 0, err
+ }
+ }
// Run the gas estimation and wrap any revertals into a custom return
- estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
+ estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap, exchangeRates, feeCurrencyBalance)
if err != nil {
if len(revert) > 0 {
return 0, newRevertError(revert)
@@ -1596,6 +1623,13 @@ Mint *hexutil.Big `json:"mint,omitempty"`
IsSystemTx *bool `json:"isSystemTx,omitempty"`
// deposit-tx post-Canyon only
DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"`
+
+ // Celo
+ FeeCurrency *common.Address `json:"feeCurrency,omitempty"`
+ MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"`
+ EthCompatible *bool `json:"ethCompatible,omitempty"`
+ GatewayFee *hexutil.Big `json:"gatewayFee,omitempty"`
+ GatewayFeeRecipient *common.Address `json:"gatewayFeeRecipient,omitempty"`
}
// newRPCTransaction returns a transaction that will serialize to the RPC
@@ -1617,6 +1651,17 @@ Value: (*hexutil.Big)(tx.Value()),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
+ // Celo
+ FeeCurrency: tx.FeeCurrency(),
+ MaxFeeInFeeCurrency: (*hexutil.Big)(tx.MaxFeeInFeeCurrency()),
+ // Unfortunately we need to set the gateway fee since this
+ // (0x3f33789ee7c52eacfe8b1a2afab8455aaf65f860dfa36f1afa466eb69bfa312e)
+ // tx on alfajores actually set it.
+ GatewayFee: (*hexutil.Big)(tx.GatewayFee()),
+ // Unfortunately we need to set the gateway fee recipient since this
+ // (0x7a2624134a8c634b38520dbb61f8fe2013e0817d446224f3d866ce3de92f4e98)
+ // tx on alfajores actually set it.
+ GatewayFeeRecipient: tx.GatewayFeeRecipient(),
}
if blockHash != (common.Hash{}) {
result.BlockHash = &blockHash
@@ -1649,6 +1694,11 @@ }
// if a legacy transaction has an EIP-155 chain id, include it explicitly
if id := tx.ChainId(); id.Sign() != 0 {
result.ChainID = (*hexutil.Big)(id)
+ }
+
+ if tx.IsCeloLegacy() {
+ // If this is a celo legacy transaction then set eth compatible to false
+ result.EthCompatible = new(bool)
}
case types.AccessListTxType:
@@ -1658,7 +1708,7 @@ result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.YParity = &yparity
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDynamicFeeTxV2Type, types.CeloDenominatedTxType:
al := tx.AccessList()
yparity := hexutil.Uint64(v.Sign())
result.Accesses = &al
@@ -1666,14 +1716,32 @@ result.ChainID = (*hexutil.Big)(tx.ChainId())
result.YParity = &yparity
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
- // if the transaction has been mined, compute the effective gas price
- if baseFee != nil && blockHash != (common.Hash{}) {
- // price = min(gasTipCap + baseFee, gasFeeCap)
+
+ // Note that celo denominated txs always have the gas price denominated in celo (the native currency)
+ isNativeFeeCurrency := tx.FeeCurrency() == nil || tx.Type() == types.CeloDenominatedTxType
+ isGingerbread := config.IsGingerbread(new(big.Int).SetUint64(blockNumber))
+ isCel2 := config.IsCel2(blockTime)
+
+ if blockHash == (common.Hash{}) {
+ // This is a pending transaction, for pending transactions we set the gas price to gas fee cap.
+ result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
+ } else if isGingerbread && isNativeFeeCurrency {
+ // Post gingerbread mined transaction with a native fee currency, we can compute the effective gas price.
result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee))
+ } else if isCel2 && tx.Type() == types.CeloDynamicFeeTxV2Type && receipt != nil {
+ // Mined post Cel2 celoDynamicFeeTxV2 transaction with non native fee currency, we get the gas price from
+ // the receipt.
+ result.GasPrice = (*hexutil.Big)(receipt.EffectiveGasPrice)
} else {
- result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
+ // Otherwise this is either a:
+ // - pre-gingerbread transaction
+ // - post-gingerbread native fee currency transaction but no base fee was provided
+ // - post-gingerbread pre-cel2 transaction with a non-native fee currency
+ // - post cel2 transaction with a non-native fee currency in a bad block (and therefore without a receipt)
+ //
+ // In these cases we can't calculate the gas price.
+ result.GasPrice = nil
}
-
case types.BlobTxType:
al := tx.AccessList()
yparity := hexutil.Uint64(v.Sign())
@@ -1728,12 +1796,12 @@ if index >= uint64(len(txs)) {
return nil
}
tx := txs[index]
- rcpt := depositTxReceipt(ctx, b.Hash(), index, backend, tx)
+ rcpt := txReceipt(ctx, b.Hash(), index, backend, tx)
return newRPCTransaction(tx, b.Hash(), b.NumberU64(), b.Time(), index, b.BaseFee(), config, rcpt)
}
-func depositTxReceipt(ctx context.Context, blockHash common.Hash, index uint64, backend Backend, tx *types.Transaction) *types.Receipt {
- if tx.Type() != types.DepositTxType {
+func txReceipt(ctx context.Context, blockHash common.Hash, index uint64, backend Backend, tx *types.Transaction) *types.Receipt {
+ if tx.Type() != types.DepositTxType && tx.Type() != types.CeloDynamicFeeTxV2Type {
return nil
}
receipts, err := backend.GetReceipts(ctx, blockHash)
@@ -1801,7 +1869,7 @@
// AccessList creates an access list for the given transaction.
// If the accesslist creation fails an error is returned.
// If the transaction itself fails, an vmErr is returned.
-func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
+func AccessList(ctx context.Context, b CeloBackend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
// Retrieve the execution context
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if db == nil || err != nil {
@@ -1839,7 +1907,17 @@ // Copy the original db so we don't modify it
statedb := db.Copy()
// Set the accesslist to the last al
args.AccessList = &accessList
- msg := args.ToMessage(header.BaseFee, true, true)
+ exchangeRates := emptyExchangeRates
+ if args.FeeCurrency != nil {
+ // Always use the header's parent here, since we want to create the list at the
+ // queried block, but want to use the exchange rates before (at the beginning of)
+ // the queried block
+ exchangeRates, err = b.GetExchangeRates(ctx, rpc.BlockNumberOrHashWithHash(header.ParentHash, false))
+ if err != nil {
+ return nil, 0, nil, fmt.Errorf("get exchange rates for block: %v err: %w", header.Hash(), err)
+ }
+ }
+ msg := args.ToMessage(header.BaseFee, true, true, exchangeRates)
// Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
@@ -1866,13 +1944,13 @@ }
// TransactionAPI exposes methods for reading and creating transaction data.
type TransactionAPI struct {
- b Backend
+ b CeloBackend
nonceLock *AddrLocker
signer types.Signer
}
// NewTransactionAPI creates a new RPC service with methods for interacting with transactions.
-func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
+func NewTransactionAPI(b CeloBackend, nonceLock *AddrLocker) *TransactionAPI {
// The signer used by the API should always be the 'latest' known one because we expect
// signers to be backwards-compatible with old transactions.
signer := types.LatestSigner(b.ChainConfig())
@@ -1985,7 +2063,7 @@ header, err := api.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
- rcpt := depositTxReceipt(ctx, blockHash, index, api.b, tx)
+ rcpt := txReceipt(ctx, blockHash, index, api.b, tx)
return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, api.b.ChainConfig(), rcpt), nil
}
@@ -2049,13 +2127,24 @@ "contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()),
- "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
+ }
+
+ // omit the effectiveGasPrice when it is nil, for Celo L1 backwards compatibility.
+ if receipt.EffectiveGasPrice != nil || chainConfig.IsGingerbread(new(big.Int).SetUint64(blockNumber)) {
+ fields["effectiveGasPrice"] = (*hexutil.Big)(receipt.EffectiveGasPrice)
}
if chainConfig.Optimism != nil && !tx.IsDepositTx() {
- fields["l1GasPrice"] = (*hexutil.Big)(receipt.L1GasPrice)
- fields["l1GasUsed"] = (*hexutil.Big)(receipt.L1GasUsed)
- fields["l1Fee"] = (*hexutil.Big)(receipt.L1Fee)
+ // These three fields are not present receipts migrated from l1 Celo
+ if receipt.L1GasPrice != nil {
+ fields["l1GasPrice"] = (*hexutil.Big)(receipt.L1GasPrice)
+ }
+ if receipt.L1GasUsed != nil {
+ fields["l1GasUsed"] = (*hexutil.Big)(receipt.L1GasUsed)
+ }
+ if receipt.L1Fee != nil {
+ fields["l1Fee"] = (*hexutil.Big)(receipt.L1Fee)
+ }
// Fields removed with Ecotone
if receipt.FeeScalar != nil {
fields["l1FeeScalar"] = receipt.FeeScalar.String()
diff --git op-geth/internal/ethapi/api_test.go Celo/internal/ethapi/api_test.go
index 91b03b7ed0175228ec1fd57bcbba965494abd801..8271b6f7c98daebffcb592a1626eb79d7f443a67 100644
--- op-geth/internal/ethapi/api_test.go
+++ Celo/internal/ethapi/api_test.go
@@ -589,6 +589,50 @@ am.AddBackend(b)
return am, acc
}
+var errCeloNotImplemented error = errors.New("Celo backend test functionality not implemented")
+
+type celoTestBackend struct {
+ *testBackend
+}
+
+func (c *celoTestBackend) GetFeeBalance(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, account common.Address, feeCurrency *common.Address) (*big.Int, error) {
+ if feeCurrency == nil {
+ header, err := c.HeaderByNumberOrHash(ctx, blockNumOrHash)
+ if err != nil {
+ return nil, fmt.Errorf("retrieve header by hash in testBackend: %w", err)
+ }
+
+ state, _, err := c.StateAndHeaderByNumber(ctx, rpc.BlockNumber(header.Number.Int64()))
+ if err != nil {
+ return nil, err
+ }
+ return state.GetBalance(account).ToBig(), nil
+ }
+ // Celo specific backend features are currently not tested
+ return nil, errCeloNotImplemented
+}
+
+func (c *celoTestBackend) GetExchangeRates(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error) {
+ var er common.ExchangeRates
+ return er, nil
+}
+
+func (c *celoTestBackend) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, feeCurrency *common.Address) (*big.Int, error) {
+ if feeCurrency == nil {
+ return value, nil
+ }
+ // Celo specific backend features are currently not tested
+ return nil, errCeloNotImplemented
+}
+
+func (c *celoTestBackend) ConvertToCelo(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, feeCurrency *common.Address) (*big.Int, error) {
+ if feeCurrency == nil {
+ return value, nil
+ }
+ // Celo specific backend features are currently not tested
+ return nil, errCeloNotImplemented
+}
+
type testBackend struct {
db ethdb.Database
chain *core.BlockChain
@@ -597,7 +641,7 @@ accman *accounts.Manager
acc accounts.Account
}
-func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
+func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *celoTestBackend {
var (
cacheConfig = &core.CacheConfig{
TrieCleanLimit: 256,
@@ -621,7 +665,9 @@ t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}
backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc}
- return backend
+ return &celoTestBackend{
+ testBackend: backend,
+ }
}
func (b *testBackend) setPendingBlock(block *types.Block) {
@@ -732,8 +778,9 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM {
if vmConfig == nil {
vmConfig = b.chain.GetVMConfig()
}
+ feeCurrencyContext := core.GetFeeCurrencyContext(header, b.ChainConfig(), state)
txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(header, b.chain, nil, b.ChainConfig(), state)
+ context := core.NewEVMBlockContext(header, b.chain, nil, b.ChainConfig(), state, feeCurrencyContext)
if blockContext != nil {
context = *blockContext
}
@@ -2960,7 +3007,10 @@ acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
genesis = &core.Genesis{
- Config: params.TestChainConfig,
+ // Use a config which disables the Cel2 flag
+ // This is necessary because Cel2 enables sending of fees to the fee handler,
+ // which in turn changes the state tree which leads to changes in hashes.
+ Config: params.TestChainConfigNoCel2,
Alloc: types.GenesisAlloc{
acc1Addr: {Balance: big.NewInt(params.Ether)},
acc2Addr: {Balance: big.NewInt(params.Ether)},
@@ -3204,7 +3254,7 @@ testRPCResponseWithFile(t, i, result, rpc, tt.file)
}
}
-func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Hash) {
+func setupReceiptBackend(t *testing.T, genBlocks int) (*celoTestBackend, []common.Hash) {
config := *params.MergedTestChainConfig
var (
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
@@ -3571,3 +3621,84 @@
func addressToHash(a common.Address) common.Hash {
return common.BytesToHash(a.Bytes())
}
+
+func TestCeloTransaction_RoundTripRpcJSON(t *testing.T) {
+ var (
+ config = params.TestChainConfig
+ signer = types.LatestSigner(config)
+ key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ tests = celoTransactionTypes(common.Address{0xde, 0xad}, config)
+ )
+ t.Parallel()
+ for i, tt := range tests {
+ tx, err := types.SignNewTx(key, signer, tt)
+ if err != nil {
+ t.Fatalf("test %d: signing failed: %v", i, err)
+ }
+
+ // Regular transaction
+ {
+ var tx2 types.Transaction
+ if data, err := json.Marshal(tx); err != nil {
+ t.Fatalf("test %d: marshalling failed; %v", i, err)
+ } else if err = tx2.UnmarshalJSON(data); err != nil {
+ t.Fatalf("test %d: sunmarshal failed: %v", i, err)
+ } else if want, have := tx.Hash(), tx2.Hash(); want != have {
+ t.Fatalf("test %d: stx changed, want %x have %x", i, want, have)
+ }
+ }
+
+ // rpcTransaction
+ {
+ var tx2 types.Transaction
+ rpcTx := newRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil, config, nil)
+ if data, err := json.Marshal(rpcTx); err != nil {
+ t.Fatalf("test %d: marshalling failed; %v", i, err)
+ } else if err = tx2.UnmarshalJSON(data); err != nil {
+ t.Fatalf("test %d: unmarshal failed: %v", i, err)
+ } else if want, have := tx.Hash(), tx2.Hash(); want != have {
+ t.Fatalf("test %d: tx changed, want %x have %x", i, want, have)
+ }
+ }
+ }
+}
+
+func celoTransactionTypes(addr common.Address, config *params.ChainConfig) []types.TxData {
+ return []types.TxData{
+ &types.CeloDynamicFeeTxV2{
+ ChainID: config.ChainID,
+ Nonce: 5,
+ GasTipCap: big.NewInt(6),
+ GasFeeCap: big.NewInt(9),
+ Gas: 7,
+ FeeCurrency: nil,
+ To: &addr,
+ Value: big.NewInt(8),
+ Data: []byte{0, 1, 2, 3, 4},
+ AccessList: types.AccessList{
+ types.AccessTuple{
+ Address: common.Address{0x2},
+ StorageKeys: []common.Hash{types.EmptyRootHash},
+ },
+ },
+ V: big.NewInt(32),
+ R: big.NewInt(10),
+ S: big.NewInt(11),
+ },
+ &types.CeloDynamicFeeTxV2{
+ ChainID: config.ChainID,
+ Nonce: 5,
+ GasTipCap: big.NewInt(6),
+ GasFeeCap: big.NewInt(9),
+ Gas: 7,
+ FeeCurrency: &common.Address{0x42},
+ To: nil,
+ Value: big.NewInt(8),
+ Data: []byte{0, 1, 2, 3, 4},
+ AccessList: types.AccessList{},
+ V: big.NewInt(32),
+ R: big.NewInt(10),
+ S: big.NewInt(11),
+ },
+ }
+}
diff --git op-geth/internal/ethapi/backend.go Celo/internal/ethapi/backend.go
index 4f5849a86984952673b61abb26edb50558357bb3..83db70000d91fd10529fe59185b04f325c671553 100644
--- op-geth/internal/ethapi/backend.go
+++ Celo/internal/ethapi/backend.go
@@ -37,8 +37,16 @@ "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
-// Backend interface provides the common API services (that are provided by
-// both full and light clients) with access to necessary functions.
+type CeloBackend interface {
+ Backend
+
+ GetFeeBalance(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, account common.Address, feeCurrency *common.Address) (*big.Int, error)
+ GetExchangeRates(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error)
+ ConvertToCurrency(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, value *big.Int, feeCurrency *common.Address) (*big.Int, error)
+ ConvertToCelo(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, value *big.Int, feeCurrency *common.Address) (*big.Int, error)
+}
+
+// Backend interface provides the common API services (that are provided by both full and light clients) with access to necessary functions.
type Backend interface {
// General Ethereum API
SyncProgress() ethereum.SyncProgress
@@ -100,7 +108,7 @@ BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
}
-func GetAPIs(apiBackend Backend) []rpc.API {
+func GetAPIs(apiBackend CeloBackend) []rpc.API {
nonceLock := new(AddrLocker)
return []rpc.API{
{
diff --git op-geth/internal/ethapi/celo_api_test.go Celo/internal/ethapi/celo_api_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cf93b4f5735fb797cb7532fb4460389f23946ce2
--- /dev/null
+++ Celo/internal/ethapi/celo_api_test.go
@@ -0,0 +1,533 @@
+package ethapi
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ // tx fields
+ nonce uint64 = 1
+ gasPrice = big.NewInt(1000)
+ gasLimit uint64 = 100000
+ feeCurrency = common.HexToAddress("0x0000000000000000000000000000000000000bbb")
+ gatewayFee = big.NewInt(500)
+ gatewayFeeRecipient = common.HexToAddress("0x0000000000000000000000000000000000000ccc")
+ to = common.HexToAddress("0x0000000000000000000000000000000000000aaa")
+ value = big.NewInt(10)
+ // block fields
+ baseFee = big.NewInt(100)
+ transactionIndex uint64 = 15
+ blockhash = common.HexToHash("0x6ba4a8c1bfe2619eb498e5296e81b1c393b13cba0198ed63dea0ee3aa619b073")
+ blockNumber uint64 = 100
+ blockTime uint64 = 100
+)
+
+func TestNewRPCTransactionLegacy(t *testing.T) {
+ config := allEnabledChainConfig()
+ // Set cel2 time to 2000 so that we don't activate the cel2 fork.
+ var cel2Time uint64 = 2000
+ config.Cel2Time = &cel2Time
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ key, err := crypto.GenerateKey()
+ require.NoError(t, err)
+
+ t.Run("WithCeloFields", func(t *testing.T) {
+ tx := types.NewTx(&types.LegacyTx{
+ Nonce: nonce,
+ GasPrice: gasPrice,
+ Gas: gasLimit,
+
+ FeeCurrency: &feeCurrency,
+ GatewayFee: gatewayFee,
+ GatewayFeeRecipient: &gatewayFeeRecipient,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+
+ CeloLegacy: true,
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, nil)
+ })
+
+ t.Run("WithoutCeloFields", func(t *testing.T) {
+ tx := types.NewTx(&types.LegacyTx{
+ Nonce: nonce,
+ GasPrice: gasPrice,
+ Gas: gasLimit,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, nil)
+ })
+}
+
+func TestNewRPCTransactionDynamicFee(t *testing.T) {
+ key, err := crypto.GenerateKey()
+ require.NoError(t, err)
+ feeCap := big.NewInt(1000)
+ tipCap := big.NewInt(100)
+
+ t.Run("PendingTransactions", func(t *testing.T) {
+ // For pending transactions we expect the gas price to be the gas fee cap.
+ gasFeeCap := func(t *testing.T, tx *types.Transaction, rpcTx *RPCTransaction) {
+ assert.Equal(t, (*hexutil.Big)(feeCap), rpcTx.GasPrice)
+ }
+ overrides := map[string]func(*testing.T, *types.Transaction, *RPCTransaction){"gasPrice": gasFeeCap}
+ config := allEnabledChainConfig()
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ // An empty bockhash signals pending transactions (I.E no mined block)
+ blockhash := common.Hash{}
+ t.Run("DynamicFeeTx", func(t *testing.T) {
+ tx := types.NewTx(&types.DynamicFeeTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+
+ t.Run("CeloDynamicFeeTxV2", func(t *testing.T) {
+ tx := types.NewTx(&types.CeloDynamicFeeTxV2{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ FeeCurrency: &feeCurrency,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+ })
+
+ t.Run("PreGingerbreadMinedDynamicTxs", func(t *testing.T) {
+ nilGasPrice := func(t *testing.T, tx *types.Transaction, rpcTx *RPCTransaction) {
+ assert.Nil(t, rpcTx.GasPrice)
+ }
+ overrides := map[string]func(*testing.T, *types.Transaction, *RPCTransaction){"gasPrice": nilGasPrice}
+ // For a pre gingerbread mined dynamic txs we expect the gas price to be unset, because without the state we
+ // cannot retrieve the base fee, and we currently have no implementation in op-geth to handle retrieving the
+ // base fee from state.
+ config := allEnabledChainConfig()
+ config.GingerbreadBlock = big.NewInt(200) // Setup config so that gingerbread is not active.
+ cel2Time := uint64(1000)
+ config.Cel2Time = &cel2Time // also deactivate cel2
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ t.Run("DynamicFeeTx", func(t *testing.T) {
+ tx := types.NewTx(&types.DynamicFeeTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+
+ t.Run("CeloDynamicFeeTx", func(t *testing.T) {
+ tx := types.NewTx(&types.CeloDynamicFeeTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ GatewayFee: gatewayFee,
+ GatewayFeeRecipient: &gatewayFeeRecipient,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+ })
+
+ t.Run("PostGingerbreadMinedDynamicTxsWithNativeFeeCurrency", func(t *testing.T) {
+ // For a post gingerbread mined dynamic tx with a native fee currency we expect the gas price to be the
+ // effective gas price calculated with the base fee available on the block.
+ effectiveGasPrice := func(t *testing.T, tx *types.Transaction, rpcTx *RPCTransaction) {
+ assert.Equal(t, (*hexutil.Big)(effectiveGasPrice(tx, baseFee)), rpcTx.GasPrice)
+ }
+ overrides := map[string]func(*testing.T, *types.Transaction, *RPCTransaction){"gasPrice": effectiveGasPrice}
+
+ config := allEnabledChainConfig()
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ t.Run("DynamicFeeTx", func(t *testing.T) {
+ tx := types.NewTx(&types.DynamicFeeTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+
+ t.Run("CeloDynamicFeeTx", func(t *testing.T) {
+ // CeloDynamicFeeTxs are deprecated after cel2 so we need to ensure cel2time is not activated
+ config := allEnabledChainConfig()
+ cel2Time := uint64(1000)
+ config.Cel2Time = &cel2Time
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ tx := types.NewTx(&types.CeloDynamicFeeTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ GatewayFee: gatewayFee,
+ GatewayFeeRecipient: &gatewayFeeRecipient,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+
+ t.Run("CeloDynamicFeeTxV2", func(t *testing.T) {
+ tx := types.NewTx(&types.CeloDynamicFeeTxV2{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+
+ // TODO unskip this when cip 66 txs are enabled currently they are not supporeted in the celo signer.
+ t.Run("CeloDenominatedTx", func(t *testing.T) {
+ t.Skip("CeloDenominatedTx is currently not supported in the celo signer")
+ tx := types.NewTx(&types.CeloDenominatedTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ FeeCurrency: &feeCurrency,
+ MaxFeeInFeeCurrency: big.NewInt(100000),
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+ })
+
+ t.Run("PostGingerbreadPreCel2MinedDynamicTxsWithNonNativeFeeCurrency", func(t *testing.T) {
+ // For a post gingerbread mined dynamic txs with a non native fee currency we expect the gas price to be unset,
+ // because without the state we cannot retrieve the base fee, and we currently have no implementation in op-geth
+ // to handle retrieving the base fee from state.
+
+ nilGasPrice := func(t *testing.T, tx *types.Transaction, rpcTx *RPCTransaction) {
+ assert.Nil(t, rpcTx.GasPrice)
+ }
+ overrides := map[string]func(*testing.T, *types.Transaction, *RPCTransaction){"gasPrice": nilGasPrice}
+
+ config := allEnabledChainConfig()
+ cel2Time := uint64(1000)
+ config.Cel2Time = &cel2Time // Deactivate cel2
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ t.Run("CeloDynamicFeeTx", func(t *testing.T) {
+ // CeloDynamicFeeTxs are deprecated after cel2 so we need to ensure cel2time is not activated
+ config := allEnabledChainConfig()
+ cel2Time := uint64(1000)
+ config.Cel2Time = &cel2Time
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ tx := types.NewTx(&types.CeloDynamicFeeTx{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ FeeCurrency: &feeCurrency,
+ GatewayFee: gatewayFee,
+ GatewayFeeRecipient: &gatewayFeeRecipient,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+
+ t.Run("CeloDynamicFeeTxV2", func(t *testing.T) {
+ tx := types.NewTx(&types.CeloDynamicFeeTxV2{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ FeeCurrency: &feeCurrency,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, nil)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+ })
+
+ t.Run("PostCel2MinedDynamicTxs", func(t *testing.T) {
+ receipt := &types.Receipt{}
+ receipt.EffectiveGasPrice = big.NewInt(1234)
+ effectiveGasPrice := func(t *testing.T, tx *types.Transaction, rpcTx *RPCTransaction) {
+ assert.Equal(t, (*hexutil.Big)(receipt.EffectiveGasPrice), rpcTx.GasPrice)
+ }
+ overrides := map[string]func(*testing.T, *types.Transaction, *RPCTransaction){"gasPrice": effectiveGasPrice}
+
+ config := allEnabledChainConfig()
+ s := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
+
+ t.Run("CeloDynamicFeeTxV2", func(t *testing.T) {
+ // For a pre gingerbread mined dynamic fee tx we expect the gas price to be unset.
+ tx := types.NewTx(&types.CeloDynamicFeeTxV2{
+ ChainID: config.ChainID,
+ Nonce: nonce,
+ Gas: gasLimit,
+ GasFeeCap: feeCap,
+ GasTipCap: tipCap,
+ FeeCurrency: &feeCurrency,
+
+ To: &to,
+ Value: value,
+ Data: []byte{},
+ })
+
+ signed, err := types.SignTx(tx, s, key)
+ require.NoError(t, err)
+
+ rpcTx := newRPCTransaction(signed, blockhash, blockNumber, blockTime, transactionIndex, baseFee, config, receipt)
+ checkTxFields(t, signed, rpcTx, s, blockhash, blockNumber, transactionIndex, overrides)
+ })
+ })
+}
+
+func allEnabledChainConfig() *params.ChainConfig {
+ zeroTime := uint64(0)
+ return ¶ms.ChainConfig{
+ ChainID: big.NewInt(params.CeloAlfajoresChainID),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ GrayGlacierBlock: big.NewInt(0),
+ ShanghaiTime: &zeroTime,
+ CancunTime: &zeroTime,
+ RegolithTime: &zeroTime,
+ CanyonTime: &zeroTime,
+ EcotoneTime: &zeroTime,
+ FjordTime: &zeroTime,
+ Cel2Time: &zeroTime,
+ GingerbreadBlock: big.NewInt(0),
+ }
+}
+
+// checkTxFields for the most part checks that the fields of the rpcTx match those of the provided tx, it allows for
+// overriding some checks by providing a map of fieldName -> overrideFunc.
+func checkTxFields(
+ t *testing.T,
+ tx *types.Transaction,
+ rpcTx *RPCTransaction,
+ signer types.Signer,
+ blockhash common.Hash,
+ blockNumber uint64,
+ transactionIndex uint64,
+ overrides map[string]func(*testing.T, *types.Transaction, *RPCTransaction),
+) {
+ // Added fields (not part of the transaction type)
+ //
+ // If blockhash is empty it signifies a pending tx and for pending txs the block hash, block number and tx index are
+ // not set on the rpcTx. on the result.
+ if blockhash == (common.Hash{}) {
+ assert.Nil(t, rpcTx.BlockHash)
+ assert.Nil(t, rpcTx.BlockNumber)
+ assert.Nil(t, rpcTx.TransactionIndex)
+ } else {
+ assert.Equal(t, &blockhash, rpcTx.BlockHash)
+ assert.Equal(t, (*hexutil.Big)(big.NewInt(int64(blockNumber))), rpcTx.BlockNumber)
+ assert.Equal(t, hexutil.Uint64(transactionIndex), *rpcTx.TransactionIndex)
+ }
+
+ from, err := types.Sender(signer, tx)
+ require.NoError(t, err)
+
+ assert.Equal(t, from, rpcTx.From)
+ assert.Equal(t, hexutil.Uint64(tx.Gas()), rpcTx.Gas)
+ assert.Equal(t, tx.To(), rpcTx.To)
+ override, ok := overrides["gasPrice"]
+ if ok {
+ override(t, tx, rpcTx)
+ } else {
+ assert.Equal(t, (*hexutil.Big)(tx.GasPrice()), rpcTx.GasPrice)
+ }
+ switch tx.Type() {
+ case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDynamicFeeTxV2Type, types.CeloDenominatedTxType:
+ assert.Equal(t, (*hexutil.Big)(tx.GasFeeCap()), rpcTx.GasFeeCap)
+ assert.Equal(t, (*hexutil.Big)(tx.GasTipCap()), rpcTx.GasTipCap)
+ default:
+ assert.Nil(t, rpcTx.GasFeeCap)
+ assert.Nil(t, rpcTx.GasTipCap)
+ }
+ assert.Equal(t, (*hexutil.Big)(tx.BlobGasFeeCap()), rpcTx.MaxFeePerBlobGas)
+ assert.Equal(t, tx.Hash(), rpcTx.Hash)
+ assert.Equal(t, (hexutil.Bytes)(tx.Data()), rpcTx.Input)
+ assert.Equal(t, hexutil.Uint64(tx.Nonce()), rpcTx.Nonce)
+ assert.Equal(t, tx.To(), rpcTx.To)
+ assert.Equal(t, (*hexutil.Big)(tx.Value()), rpcTx.Value)
+ assert.Equal(t, hexutil.Uint64(tx.Type()), rpcTx.Type)
+ switch tx.Type() {
+ case types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDynamicFeeTxV2Type, types.CeloDenominatedTxType, types.BlobTxType:
+ assert.Equal(t, tx.AccessList(), *rpcTx.Accesses)
+ default:
+ assert.Nil(t, rpcTx.Accesses)
+ }
+
+ assert.Equal(t, (*hexutil.Big)(tx.ChainId()), rpcTx.ChainID)
+ assert.Equal(t, tx.BlobHashes(), rpcTx.BlobVersionedHashes)
+
+ v, r, s := tx.RawSignatureValues()
+ assert.Equal(t, (*hexutil.Big)(v), rpcTx.V)
+ assert.Equal(t, (*hexutil.Big)(r), rpcTx.R)
+ assert.Equal(t, (*hexutil.Big)(s), rpcTx.S)
+
+ switch tx.Type() {
+ case types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDynamicFeeTxV2Type, types.CeloDenominatedTxType, types.BlobTxType:
+ yparity := (hexutil.Uint64)(v.Sign())
+ assert.Equal(t, &yparity, rpcTx.YParity)
+ default:
+ assert.Nil(t, rpcTx.YParity)
+ }
+
+ // optimism fields
+ switch tx.Type() {
+ case types.DepositTxType:
+ assert.Equal(t, tx.SourceHash(), rpcTx.SourceHash)
+ assert.Equal(t, tx.Mint(), rpcTx.Mint)
+ assert.Equal(t, tx.IsSystemTx(), rpcTx.IsSystemTx)
+ default:
+ assert.Nil(t, rpcTx.SourceHash)
+ assert.Nil(t, rpcTx.Mint)
+ assert.Nil(t, rpcTx.IsSystemTx)
+ }
+
+ assert.Nil(t, rpcTx.DepositReceiptVersion)
+
+ // celo fields
+ assert.Equal(t, tx.FeeCurrency(), rpcTx.FeeCurrency)
+ assert.Equal(t, (*hexutil.Big)(tx.MaxFeeInFeeCurrency()), rpcTx.MaxFeeInFeeCurrency)
+ if tx.Type() == types.LegacyTxType && tx.IsCeloLegacy() {
+ assert.Equal(t, false, *rpcTx.EthCompatible)
+ } else {
+ assert.Nil(t, rpcTx.EthCompatible)
+ }
+ assert.Equal(t, (*hexutil.Big)(tx.GatewayFee()), rpcTx.GatewayFee)
+ assert.Equal(t, tx.GatewayFeeRecipient(), rpcTx.GatewayFeeRecipient)
+}
diff --git op-geth/internal/ethapi/celo_block_receipt.go Celo/internal/ethapi/celo_block_receipt.go
new file mode 100644
index 0000000000000000000000000000000000000000..e89b3a684a9f46082248e3bdfe458cfdc2242d58
--- /dev/null
+++ Celo/internal/ethapi/celo_block_receipt.go
@@ -0,0 +1,60 @@
+package ethapi
+
+import (
+ "context"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+// GetBlockReceipt returns "system calls" receipt for the block with the given block hash.
+func (s *BlockChainAPI) GetBlockReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
+ block, err := s.b.BlockByHash(ctx, hash)
+ if block == nil || err != nil {
+ // If no header with that hash is found, err gives "header for hash not found".
+ // But we return nil with no error, to match the behavior of eth_getBlockByHash and eth_getTransactionReceipt in these cases.
+ return nil, nil
+ }
+ index := block.Transactions().Len()
+ blockNumber := block.NumberU64()
+ receipts, err := s.b.GetReceipts(ctx, block.Hash())
+ // GetReceipts() doesn't return an error if things go wrong, so we also check len(receipts)
+ if err != nil || len(receipts) < index {
+ return nil, err
+ }
+
+ var receipt *types.Receipt
+ if len(receipts) == index {
+ // The block didn't have any logs from system calls and no receipt was created.
+ // So we create an empty receipt to return, similarly to how system receipts are created.
+ receipt = types.NewReceipt(nil, false, 0)
+ receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
+ } else {
+ receipt = receipts[index]
+ }
+ return marshalBlockReceipt(receipt, hash, blockNumber, index), nil
+}
+
+// marshalBlockReceipt marshals a Celo block receipt into a JSON object. See https://docs.celo.org/developer/migrate/from-ethereum#core-contract-calls
+func marshalBlockReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, index int) map[string]interface{} {
+ fields := map[string]interface{}{
+ "blockHash": blockHash,
+ "blockNumber": hexutil.Uint64(blockNumber),
+ "transactionHash": blockHash,
+ "transactionIndex": hexutil.Uint64(index),
+ "from": common.Address{},
+ "to": nil,
+ "gasUsed": hexutil.Uint64(0),
+ "cumulativeGasUsed": hexutil.Uint64(0),
+ "contractAddress": nil,
+ "logs": receipt.Logs,
+ "logsBloom": receipt.Bloom,
+ "type": hexutil.Uint(0),
+ "status": hexutil.Uint(types.ReceiptStatusSuccessful),
+ }
+ if receipt.Logs == nil {
+ fields["logs"] = []*types.Log{}
+ }
+ return fields
+}
diff --git op-geth/internal/ethapi/simulate.go Celo/internal/ethapi/simulate.go
index c42ede29db1c5d2d535ca73258e792309a0a5e16..99675ba94f0f95870537f809e67e2b1c635d8e92 100644
--- op-geth/internal/ethapi/simulate.go
+++ Celo/internal/ethapi/simulate.go
@@ -168,7 +168,8 @@ excess = eip4844.CalcExcessBlobGas(0, 0)
}
header.ExcessBlobGas = &excess
}
- blockContext := core.NewEVMBlockContext(header, sim.newSimulatedChainContext(ctx, headers), nil, sim.chainConfig, sim.state)
+ feeCurrencyContext := core.GetFeeCurrencyContext(header, sim.chainConfig, sim.state)
+ blockContext := core.NewEVMBlockContext(header, sim.newSimulatedChainContext(ctx, headers), nil, sim.chainConfig, sim.state, feeCurrencyContext)
if block.BlockOverrides.BlobBaseFee != nil {
blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt()
}
@@ -207,7 +208,7 @@ tx := call.ToTransaction(types.DynamicFeeTxType)
txes[i] = tx
tracer.reset(tx.Hash(), uint(i))
// EoA check is always skipped, even in validation mode.
- msg := call.ToMessage(header.BaseFee, !sim.validate, true)
+ msg := call.ToMessage(header.BaseFee, !sim.validate, true, blockContext.FeeCurrencyContext.ExchangeRates)
evm.Reset(core.NewEVMTxContext(msg), sim.state)
result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, sim.gp)
if err != nil {
diff --git op-geth/internal/ethapi/transaction_args.go Celo/internal/ethapi/transaction_args.go
index f9835a96dabfa220fade85ff6a7d51701c3d5c71..333ee67ceac1c2b78fbe5d9ba205d38053024f5b 100644
--- op-geth/internal/ethapi/transaction_args.go
+++ Celo/internal/ethapi/transaction_args.go
@@ -25,6 +25,7 @@ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
@@ -74,6 +75,10 @@ Proofs []kzg4844.Proof `json:"proofs"`
// This configures whether blobs are allowed to be passed.
blobSidecarAllowed bool
+
+ // Celo specific:
+ FeeCurrency *common.Address `json:"feeCurrency,omitempty"` // CIP-64, CIP-66
+ MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"` // CIP-66
}
// from retrieves the transaction sender address.
@@ -96,7 +101,7 @@ return nil
}
// setDefaults fills in default values for unspecified tx fields.
-func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGasEstimation bool) error {
+func (args *TransactionArgs) setDefaults(ctx context.Context, b CeloBackend, skipGasEstimation bool) error {
if err := args.setBlobTxSidecar(ctx); err != nil {
return err
}
@@ -158,6 +163,9 @@ Data: (*hexutil.Bytes)(&data),
AccessList: args.AccessList,
BlobFeeCap: args.BlobFeeCap,
BlobHashes: args.BlobHashes,
+
+ FeeCurrency: args.FeeCurrency,
+ MaxFeeInFeeCurrency: args.MaxFeeInFeeCurrency,
}
latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, b.RPCGasCap())
@@ -183,7 +191,7 @@ return nil
}
// setFeeDefaults fills in default fee values for unspecified tx fields.
-func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) error {
+func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b CeloBackend) error {
head := b.CurrentHeader()
// Sanity check the EIP-4844 fee parameters.
if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 {
@@ -201,6 +209,10 @@ // This allows users who are not yet synced past London to get defaults for
// other tx values. See https://github.com/ethereum/go-ethereum/pull/23274
// for more information.
eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil
+
+ if args.MaxFeeInFeeCurrency != nil && args.FeeCurrency == nil {
+ return errors.New("feeCurrency must be set when maxFeeInFeeCurrency is given")
+ }
// Sanity check the EIP-1559 fee parameters if present.
if args.GasPrice == nil && eip1559ParamsSet {
if args.MaxFeePerGas.ToInt().Sign() == 0 {
@@ -243,7 +255,7 @@ return nil
}
// setCancunFeeDefaults fills in reasonable default fee values for unspecified fields.
-func (args *TransactionArgs) setCancunFeeDefaults(ctx context.Context, head *types.Header, b Backend) error {
+func (args *TransactionArgs) setCancunFeeDefaults(ctx context.Context, head *types.Header, b CeloBackend) error {
// Set maxFeePerBlobGas if it is missing.
if args.BlobHashes != nil && args.BlobFeeCap == nil {
var excessBlobGas uint64
@@ -252,6 +264,20 @@ excessBlobGas = *head.ExcessBlobGas
}
// ExcessBlobGas must be set for a Cancun block.
blobBaseFee := eip4844.CalcBlobFee(excessBlobGas)
+ if args.IsFeeCurrencyDenominated() {
+ // wether the blob-fee will be used like that in Cel2 or not,
+ // at least this keeps it consistent with the rest of the gas-fees
+ var err error
+ blobBaseFee, err = b.ConvertToCurrency(
+ ctx,
+ rpc.BlockNumberOrHashWithHash(head.Hash(), false),
+ blobBaseFee,
+ args.FeeCurrency,
+ )
+ if err != nil {
+ return fmt.Errorf("can't convert blob-fee to fee-currency: %w", err)
+ }
+ }
// Set the max fee to be 2 times larger than the previous block's blob base fee.
// The additional slack allows the tx to not become invalidated if the base
// fee is rising.
@@ -262,13 +288,24 @@ return nil
}
// setLondonFeeDefaults fills in reasonable default fee values for unspecified fields.
-func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *types.Header, b Backend) error {
+func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *types.Header, b CeloBackend) error {
// Set maxPriorityFeePerGas if it is missing.
if args.MaxPriorityFeePerGas == nil {
tip, err := b.SuggestGasTipCap(ctx)
if err != nil {
return err
}
+ if args.IsFeeCurrencyDenominated() {
+ tip, err = b.ConvertToCurrency(
+ ctx,
+ rpc.BlockNumberOrHashWithHash(head.Hash(), false),
+ tip,
+ args.FeeCurrency,
+ )
+ if err != nil {
+ return fmt.Errorf("can't convert suggested gasTipCap to fee-currency: %w", err)
+ }
+ }
args.MaxPriorityFeePerGas = (*hexutil.Big)(tip)
}
// Set maxFeePerGas if it is missing.
@@ -276,9 +313,22 @@ if args.MaxFeePerGas == nil {
// Set the max fee to be 2 times larger than the previous block's base fee.
// The additional slack allows the tx to not become invalidated if the base
// fee is rising.
+ baseFee := head.BaseFee
+ if args.IsFeeCurrencyDenominated() {
+ var err error
+ baseFee, err = b.ConvertToCurrency(
+ ctx,
+ rpc.BlockNumberOrHashWithHash(head.Hash(), false),
+ baseFee,
+ args.FeeCurrency,
+ )
+ if err != nil {
+ return fmt.Errorf("can't convert base-fee to fee-currency: %w", err)
+ }
+ }
val := new(big.Int).Add(
args.MaxPriorityFeePerGas.ToInt(),
- new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
+ new(big.Int).Mul(baseFee, big.NewInt(2)),
)
args.MaxFeePerGas = (*hexutil.Big)(val)
}
@@ -421,11 +471,14 @@ // ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
-func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool) *core.Message {
+func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool, exchangeRates common.ExchangeRates) *core.Message {
var (
gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
+
+ // Celo specific
+ maxFeeInFeeCurrency *big.Int
)
if baseFee == nil {
gasPrice = args.GasPrice.ToInt()
@@ -443,6 +496,14 @@ gasTipCap = args.MaxPriorityFeePerGas.ToInt()
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(big.Int)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
+ if args.IsFeeCurrencyDenominated() {
+ var err error
+ baseFee, err = exchange.ConvertCeloToCurrency(exchangeRates, args.FeeCurrency, baseFee)
+ if err != nil {
+ log.Error("can't convert base-fee to fee-currency", "err", err)
+ baseFee = common.Big1
+ }
+ }
gasPrice = math.BigMin(new(big.Int).Add(gasTipCap, baseFee), gasFeeCap)
}
}
@@ -451,6 +512,9 @@ var accessList types.AccessList
if args.AccessList != nil {
accessList = *args.AccessList
}
+ if args.MaxFeeInFeeCurrency != nil {
+ maxFeeInFeeCurrency = args.MaxFeeInFeeCurrency.ToInt()
+ }
return &core.Message{
From: args.from(),
To: args.To,
@@ -466,6 +530,10 @@ BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
BlobHashes: args.BlobHashes,
SkipNonceChecks: skipNonceCheck,
SkipFromEOACheck: skipEoACheck,
+
+ // Celo specific:
+ FeeCurrency: args.FeeCurrency,
+ MaxFeeInFeeCurrency: maxFeeInFeeCurrency,
}
}
@@ -518,16 +586,47 @@ al := types.AccessList{}
if args.AccessList != nil {
al = *args.AccessList
}
- data = &types.DynamicFeeTx{
- To: args.To,
- ChainID: (*big.Int)(args.ChainID),
- Nonce: uint64(*args.Nonce),
- Gas: uint64(*args.Gas),
- GasFeeCap: (*big.Int)(args.MaxFeePerGas),
- GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
- Value: (*big.Int)(args.Value),
- Data: args.data(),
- AccessList: al,
+ if args.FeeCurrency != nil {
+ if args.IsFeeCurrencyDenominated() {
+ data = &types.CeloDynamicFeeTxV2{
+ To: args.To,
+ ChainID: (*big.Int)(args.ChainID),
+ Nonce: uint64(*args.Nonce),
+ Gas: uint64(*args.Gas),
+ GasFeeCap: (*big.Int)(args.MaxFeePerGas),
+ GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
+ Value: (*big.Int)(args.Value),
+ Data: args.data(),
+ AccessList: al,
+ FeeCurrency: args.FeeCurrency,
+ }
+ } else {
+ data = &types.CeloDenominatedTx{
+ To: args.To,
+ ChainID: (*big.Int)(args.ChainID),
+ Nonce: uint64(*args.Nonce),
+ Gas: uint64(*args.Gas),
+ GasFeeCap: (*big.Int)(args.MaxFeePerGas),
+ GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
+ Value: (*big.Int)(args.Value),
+ Data: args.data(),
+ AccessList: al,
+ FeeCurrency: args.FeeCurrency,
+ MaxFeeInFeeCurrency: (*big.Int)(args.MaxFeeInFeeCurrency),
+ }
+ }
+ } else {
+ data = &types.DynamicFeeTx{
+ To: args.To,
+ ChainID: (*big.Int)(args.ChainID),
+ Nonce: uint64(*args.Nonce),
+ Gas: uint64(*args.Gas),
+ GasFeeCap: (*big.Int)(args.MaxFeePerGas),
+ GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
+ Value: (*big.Int)(args.Value),
+ Data: args.data(),
+ AccessList: al,
+ }
}
case types.AccessListTxType:
@@ -559,3 +658,10 @@ // IsEIP4844 returns an indicator if the args contains EIP4844 fields.
func (args *TransactionArgs) IsEIP4844() bool {
return args.BlobHashes != nil || args.BlobFeeCap != nil
}
+
+// IsFeeCurrencyDenominated returns whether the gas-price related
+// fields are denominated in a given fee currency or in the native token.
+// This effectively is only true for CIP-64 transactions.
+func (args *TransactionArgs) IsFeeCurrencyDenominated() bool {
+ return args.FeeCurrency != nil && args.MaxFeeInFeeCurrency == nil
+}
diff --git op-geth/internal/ethapi/transaction_args_test.go Celo/internal/ethapi/transaction_args_test.go
index 8b9b7fb66a8127aaea9d7bf0f322ee421c92ad92..c202ac25950c469a677ea31477858764f3cf90a4 100644
--- op-geth/internal/ethapi/transaction_args_test.go
+++ Celo/internal/ethapi/transaction_args_test.go
@@ -53,11 +53,14 @@ err error
}
var (
- b = newBackendMock()
- zero = (*hexutil.Big)(big.NewInt(0))
- fortytwo = (*hexutil.Big)(big.NewInt(42))
- maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
- al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
+ b = newCeloBackendMock()
+ zero = (*hexutil.Big)(big.NewInt(0))
+ fortytwo = (*hexutil.Big)(big.NewInt(42))
+ maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
+ al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
+ feeCurrency = common.BigToAddress(big.NewInt(42))
+ eightyfour = (*hexutil.Big)(big.NewInt(84))
+ doubleMaxFee = (*hexutil.Big)(new(big.Int).Mul(maxFee.ToInt(), big.NewInt(2)))
)
tests := []test{
@@ -230,6 +233,37 @@ &TransactionArgs{BlobHashes: []common.Hash{}, MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
&TransactionArgs{BlobHashes: []common.Hash{}, BlobFeeCap: (*hexutil.Big)(big.NewInt(4)), MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
nil,
},
+ // CIP-64
+ {
+ "Fee-currency denominated tx, set maxPriorityFeePerGas in converted valued",
+ "cancun",
+ &TransactionArgs{MaxFeePerGas: doubleMaxFee, FeeCurrency: &feeCurrency},
+ // maxPriorityFeePerGas is double in feeCurrency
+ &TransactionArgs{MaxFeePerGas: doubleMaxFee, MaxPriorityFeePerGas: eightyfour, FeeCurrency: &feeCurrency},
+ nil,
+ },
+ {
+ "Fee-currency denominated tx, set maxFeePerGas in converted valued",
+ "cancun",
+ &TransactionArgs{MaxPriorityFeePerGas: eightyfour, FeeCurrency: &feeCurrency},
+ &TransactionArgs{MaxFeePerGas: doubleMaxFee, MaxPriorityFeePerGas: eightyfour, FeeCurrency: &feeCurrency},
+ nil,
+ },
+ // CIP-66
+ {
+ "CIP-66 transaction, maxPriorityFeePerGas gets set in non-converted value",
+ "cancun",
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxFeeInFeeCurrency: fortytwo, FeeCurrency: &feeCurrency},
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo, MaxFeeInFeeCurrency: fortytwo, FeeCurrency: &feeCurrency},
+ nil,
+ },
+ {
+ "set maxFeeInFeeCurrency without feeCurrency",
+ "cancun",
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo, MaxFeeInFeeCurrency: fortytwo},
+ nil,
+ errors.New("feeCurrency must be set when maxFeeInFeeCurrency is given"),
+ },
}
ctx := context.Background()
@@ -254,6 +288,41 @@ if !reflect.DeepEqual(got, test.want) {
t.Fatalf("test %d (%s): did not fill defaults as expected: (got: %v, want: %v)", i, test.name, got, test.want)
}
}
+}
+
+type celoBackendMock struct {
+ *backendMock
+}
+
+func newCeloBackendMock() *celoBackendMock {
+ return &celoBackendMock{
+ backendMock: newBackendMock(),
+ }
+}
+
+func (c *celoBackendMock) GetFeeBalance(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, account common.Address, feeCurrency *common.Address) (*big.Int, error) {
+ // Celo specific backend features are currently not tested
+ return nil, errCeloNotImplemented
+}
+
+func (c *celoBackendMock) GetExchangeRates(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error) {
+ var er common.ExchangeRates
+ // This Celo specific backend features are currently not tested
+ return er, errCeloNotImplemented
+}
+
+func (c *celoBackendMock) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, fromFeeCurrency *common.Address) (*big.Int, error) {
+ if fromFeeCurrency == nil {
+ return value, nil
+ }
+ return new(big.Int).Mul(value, big.NewInt(2)), nil
+}
+
+func (c *celoBackendMock) ConvertToCelo(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, toFeeCurrency *common.Address) (*big.Int, error) {
+ if toFeeCurrency == nil {
+ return value, nil
+ }
+ return new(big.Int).Div(value, big.NewInt(2)), nil
}
type backendMock struct {
diff --git op-geth/miner/celo_defaults.go Celo/miner/celo_defaults.go
new file mode 100644
index 0000000000000000000000000000000000000000..be843d1bc991fcbf6fa80bf4b291617e110d0716
--- /dev/null
+++ Celo/miner/celo_defaults.go
@@ -0,0 +1,29 @@
+package miner
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// cStables addresses on mainnet
+var (
+ cUSD_TOKEN = common.HexToAddress("0x765DE816845861e75A25fCA122bb6898B8B1282a")
+ cEUR_TOKEN = common.HexToAddress("0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73")
+ cREAL_TOKEN = common.HexToAddress("0xe8537a3d056DA446677B9E9d6c5dB704EaAb4787")
+ USDC_TOKEN = common.HexToAddress("0xcebA9300f2b948710d2653dD7B07f33A8B32118C")
+ USDT_TOKEN = common.HexToAddress("0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e")
+)
+
+// default limits default fraction
+const DefaultFeeCurrencyLimit = 0.5
+
+// default limits configuration
+var DefaultFeeCurrencyLimits = map[uint64]map[common.Address]float64{
+ params.CeloMainnetChainID: {
+ cUSD_TOKEN: 0.9,
+ USDT_TOKEN: 0.9,
+ USDC_TOKEN: 0.9,
+ cEUR_TOKEN: 0.5,
+ cREAL_TOKEN: 0.5,
+ },
+}
diff --git op-geth/miner/currency_blocklist.go Celo/miner/currency_blocklist.go
new file mode 100644
index 0000000000000000000000000000000000000000..4081a2eb81131ec34447af4bb47b9dc8d5562942
--- /dev/null
+++ Celo/miner/currency_blocklist.go
@@ -0,0 +1,134 @@
+package miner
+
+import (
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+const hours uint64 = 60 * 60
+
+var EvictionTimeoutSeconds uint64 = 2 * hours
+
+type AddressBlocklist struct {
+ mux *sync.RWMutex
+ currencies map[common.Address]*types.Header
+ // fee-currencies blocked at headers with an older timestamp
+ // will get evicted when evict() is called
+ headerEvictionTimeoutSeconds uint64
+ oldestHeader *types.Header
+}
+
+func NewAddressBlocklist() *AddressBlocklist {
+ return &AddressBlocklist{
+ mux: &sync.RWMutex{},
+ currencies: map[common.Address]*types.Header{},
+ headerEvictionTimeoutSeconds: EvictionTimeoutSeconds,
+ oldestHeader: nil,
+ }
+}
+
+func (b *AddressBlocklist) FilterAllowlist(allowlist common.AddressSet, latest *types.Header) common.AddressSet {
+ b.mux.RLock()
+ defer b.mux.RUnlock()
+
+ filtered := common.AddressSet{}
+ for a := range allowlist {
+ if !b.isBlocked(a, latest) {
+ filtered[a] = struct{}{}
+ }
+ }
+ return filtered
+}
+
+func (b *AddressBlocklist) IsBlocked(currency common.Address, latest *types.Header) bool {
+ b.mux.RLock()
+ defer b.mux.RUnlock()
+
+ return b.isBlocked(currency, latest)
+}
+
+func (b *AddressBlocklist) Remove(currency common.Address) bool {
+ b.mux.Lock()
+ defer b.mux.Unlock()
+
+ h, ok := b.currencies[currency]
+ if !ok {
+ return false
+ }
+ delete(b.currencies, currency)
+ if b.oldestHeader.Time >= h.Time {
+ b.resetOldestHeader()
+ }
+ return ok
+}
+
+func (b *AddressBlocklist) Add(currency common.Address, head types.Header) {
+ b.mux.Lock()
+ defer b.mux.Unlock()
+
+ if b.oldestHeader == nil || b.oldestHeader.Time > head.Time {
+ b.oldestHeader = &head
+ }
+ b.currencies[currency] = &head
+}
+
+func (b *AddressBlocklist) Evict(latest *types.Header) []common.Address {
+ b.mux.Lock()
+ defer b.mux.Unlock()
+ return b.evict(latest)
+}
+
+func (b *AddressBlocklist) resetOldestHeader() {
+ if len(b.currencies) == 0 {
+ b.oldestHeader = nil
+ return
+ }
+ for _, v := range b.currencies {
+ if b.oldestHeader == nil {
+ b.oldestHeader = v
+ continue
+ }
+ if v.Time < b.oldestHeader.Time {
+ b.oldestHeader = v
+ }
+ }
+}
+
+func (b *AddressBlocklist) evict(latest *types.Header) []common.Address {
+ evicted := []common.Address{}
+ if latest == nil {
+ return evicted
+ }
+
+ if b.oldestHeader == nil || !b.headerEvicted(b.oldestHeader, latest) {
+ // nothing set yet
+ return evicted
+ }
+ for feeCurrencyAddress, addedHeader := range b.currencies {
+ if b.headerEvicted(addedHeader, latest) {
+ delete(b.currencies, feeCurrencyAddress)
+ evicted = append(evicted, feeCurrencyAddress)
+ }
+ }
+ b.resetOldestHeader()
+ return evicted
+}
+
+func (b *AddressBlocklist) headerEvicted(h, latest *types.Header) bool {
+ return h.Time+b.headerEvictionTimeoutSeconds < latest.Time
+}
+
+func (b *AddressBlocklist) isBlocked(currency common.Address, latest *types.Header) bool {
+ h, exists := b.currencies[currency]
+ if !exists {
+ return false
+ }
+ if latest == nil {
+ // if no latest block provided to check eviction,
+ // assume the currency is blocked
+ return true
+ }
+ return !b.headerEvicted(h, latest)
+}
diff --git op-geth/miner/currency_blocklist_test.go Celo/miner/currency_blocklist_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a26d7392ae762c8e80abf7c71f257f6ad44bcf47
--- /dev/null
+++ Celo/miner/currency_blocklist_test.go
@@ -0,0 +1,93 @@
+package miner
+
+import (
+ "math"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ feeCurrency1 = common.BigToAddress(big.NewInt(1))
+ feeCurrency2 = common.BigToAddress(big.NewInt(2))
+ header = types.Header{Time: 1111111111111}
+)
+
+func HeaderAfter(h types.Header, deltaSeconds int64) *types.Header {
+ if h.Time > math.MaxInt64 {
+ panic("int64 overflow")
+ }
+ t := int64(h.Time) + deltaSeconds
+ if t < 0 {
+ panic("uint64 underflow")
+ }
+ return &types.Header{Time: uint64(t)}
+}
+
+func TestBlocklistEviction(t *testing.T) {
+ bl := NewAddressBlocklist()
+ bl.Add(feeCurrency1, header)
+
+ // latest header is before eviction time
+ assert.True(t, bl.IsBlocked(feeCurrency1, HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)))
+ // latest header is after eviction time
+ assert.False(t, bl.IsBlocked(feeCurrency1, HeaderAfter(header, int64(EvictionTimeoutSeconds)+1)))
+
+ // check filter allowlist removes the currency from the allowlist
+ assert.Equal(t, len(bl.FilterAllowlist(
+ common.NewAddressSet(feeCurrency1),
+ HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)),
+ ), 0)
+
+ // permanently delete the currency from the blocklist
+ bl.Evict(HeaderAfter(header, int64(EvictionTimeoutSeconds)+1))
+
+ // now the currency is removed from the cache, so the currency is not blocked even in earlier headers
+ assert.False(t, bl.IsBlocked(feeCurrency1, HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)))
+
+ // check filter allowlist doesn't change the allowlist
+ assert.Equal(t, len(bl.FilterAllowlist(
+ common.NewAddressSet(feeCurrency1),
+ HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)),
+ ), 1)
+}
+
+func TestBlocklistAddAfterEviction(t *testing.T) {
+ bl := NewAddressBlocklist()
+ bl.Add(feeCurrency1, header)
+ bl.Evict(HeaderAfter(header, int64(EvictionTimeoutSeconds)+1))
+
+ header2 := HeaderAfter(header, 10)
+ bl.Add(feeCurrency2, *header2)
+
+ // make sure the feeCurrency2 behaves as expected
+ assert.True(t, bl.IsBlocked(feeCurrency2, HeaderAfter(*header2, int64(EvictionTimeoutSeconds)-1)))
+ assert.False(t, bl.IsBlocked(feeCurrency2, HeaderAfter(*header2, int64(EvictionTimeoutSeconds)+1)))
+}
+
+func TestBlocklistRemove(t *testing.T) {
+ bl := NewAddressBlocklist()
+ bl.Add(feeCurrency1, header)
+ bl.Add(feeCurrency2, header)
+ bl.Remove(feeCurrency1)
+
+ assert.False(t, bl.IsBlocked(feeCurrency1, HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)))
+ assert.True(t, bl.IsBlocked(feeCurrency2, HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)))
+}
+
+func TestBlocklistAddAfterRemove(t *testing.T) {
+ bl := NewAddressBlocklist()
+ bl.Add(feeCurrency1, header)
+ bl.Remove(feeCurrency1)
+ assert.False(t, bl.IsBlocked(feeCurrency1, HeaderAfter(header, int64(EvictionTimeoutSeconds)-1)))
+
+ header2 := HeaderAfter(header, 10)
+ bl.Add(feeCurrency2, *header2)
+
+ // make sure the feeCurrency2 behaves as expected
+ assert.True(t, bl.IsBlocked(feeCurrency2, HeaderAfter(*header2, int64(EvictionTimeoutSeconds)-1)))
+ assert.False(t, bl.IsBlocked(feeCurrency2, HeaderAfter(*header2, int64(EvictionTimeoutSeconds)+1)))
+}
diff --git op-geth/miner/miner.go Celo/miner/miner.go
index 447cf4ccbd2f59fac1c9c1f7d391f2d74c726d01..080000a5300ba87d8b8f9b05b056a87354f40e41 100644
--- op-geth/miner/miner.go
+++ Celo/miner/miner.go
@@ -66,6 +66,10 @@
EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value
MaxDATxSize *big.Int `toml:",omitempty"` // if non-nil, don't include any txs with data availability size larger than this in any built block
MaxDABlockSize *big.Int `toml:",omitempty"` // if non-nil, then don't build a block requiring more than this amount of total data availability
+
+ // Celo:
+ FeeCurrencyDefault float64 // Default fraction of block gas limit
+ FeeCurrencyLimits map[common.Address]float64 // Fee currency-to-limit fraction mapping
}
// DefaultConfig contains default settings for miner.
@@ -78,6 +82,8 @@ // consensus-layer usually will wait a half slot of time(6s)
// for payload generation. It should be enough for Geth to
// run 3 rounds.
Recommit: 2 * time.Second,
+
+ FeeCurrencyDefault: DefaultFeeCurrencyLimit,
}
// Miner is the main object which takes care of submitting new work to consensus
@@ -96,6 +102,8 @@ backend Backend
lifeCtxCancel context.CancelFunc
lifeCtx context.Context
+
+ feeCurrencyBlocklist *AddressBlocklist
}
// New creates a new miner with provided config.
@@ -112,6 +120,8 @@ pending: &pending{},
// To interrupt background tasks that may be attached to external processes
lifeCtxCancel: cancel,
lifeCtx: ctx,
+
+ feeCurrencyBlocklist: NewAddressBlocklist(),
}
}
diff --git op-geth/miner/ordering.go Celo/miner/ordering.go
index bcf7af46e8912eda5d169580ee777e7e419b53df..b270aca5ebdf2ae0dbb6b900bfef2347928edc5d 100644
--- op-geth/miner/ordering.go
+++ Celo/miner/ordering.go
@@ -21,6 +21,7 @@ "container/heap"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
@@ -36,17 +37,36 @@
// newTxWithMinerFee creates a wrapped transaction, calculating the effective
// miner gasTipCap if a base fee is provided.
// Returns error in case of a negative effective miner gasTipCap.
-func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *uint256.Int) (*txWithMinerFee, error) {
+func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *uint256.Int, rates common.ExchangeRates) (*txWithMinerFee, error) {
tip := new(uint256.Int).Set(tx.GasTipCap)
if baseFee != nil {
- if tx.GasFeeCap.Cmp(baseFee) < 0 {
+ baseFeeConverted := baseFee
+ if tx.FeeCurrency != nil {
+ baseFeeBig, err := exchange.ConvertCeloToCurrency(rates, tx.FeeCurrency, baseFee.ToBig())
+ if err != nil {
+ return nil, err
+ }
+ baseFeeConverted = uint256.MustFromBig(baseFeeBig)
+ }
+
+ if tx.GasFeeCap.Cmp(baseFeeConverted) < 0 {
return nil, types.ErrGasFeeCapTooLow
}
- tip = new(uint256.Int).Sub(tx.GasFeeCap, baseFee)
+ tip = new(uint256.Int).Sub(tx.GasFeeCap, baseFeeConverted)
if tip.Gt(tx.GasTipCap) {
tip = tx.GasTipCap
}
}
+
+ // Convert tip back into celo if the transaction is in a different currency
+ if tx.FeeCurrency != nil {
+ tipBig, err := exchange.ConvertCurrencyToCelo(rates, tx.FeeCurrency, tip.ToBig())
+ if err != nil {
+ return nil, err
+ }
+ tip = uint256.MustFromBig(tipBig)
+ }
+
return &txWithMinerFee{
tx: tx,
from: from,
@@ -91,6 +111,9 @@ txs map[common.Address][]*txpool.LazyTransaction // Per account nonce-sorted list of transactions
heads txByPriceAndTime // Next transaction for each unique account (price heap)
signer types.Signer // Signer for the set of transactions
baseFee *uint256.Int // Current base fee
+
+ // Celo specific
+ exchangeRates common.ExchangeRates
}
// newTransactionsByPriceAndNonce creates a transaction set that can retrieve
@@ -98,7 +121,7 @@ // price sorted transactions in a nonce-honouring way.
//
// Note, the input map is reowned so the caller should not interact any more with
// if after providing it to the constructor.
-func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address][]*txpool.LazyTransaction, baseFee *big.Int) *transactionsByPriceAndNonce {
+func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address][]*txpool.LazyTransaction, baseFee *big.Int, rates common.ExchangeRates) *transactionsByPriceAndNonce {
// Convert the basefee from header format to uint256 format
var baseFeeUint *uint256.Int
if baseFee != nil {
@@ -107,7 +130,7 @@ }
// Initialize a price and received time based heap with the head transactions
heads := make(txByPriceAndTime, 0, len(txs))
for from, accTxs := range txs {
- wrapped, err := newTxWithMinerFee(accTxs[0], from, baseFeeUint)
+ wrapped, err := newTxWithMinerFee(accTxs[0], from, baseFeeUint, rates)
if err != nil {
delete(txs, from)
continue
@@ -119,10 +142,11 @@ heap.Init(&heads)
// Assemble and return the transaction set
return &transactionsByPriceAndNonce{
- txs: txs,
- heads: heads,
- signer: signer,
- baseFee: baseFeeUint,
+ txs: txs,
+ heads: heads,
+ signer: signer,
+ baseFee: baseFeeUint,
+ exchangeRates: rates,
}
}
@@ -138,7 +162,7 @@ // Shift replaces the current best head with the next one from the same account.
func (t *transactionsByPriceAndNonce) Shift() {
acc := t.heads[0].from
if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
- if wrapped, err := newTxWithMinerFee(txs[0], acc, t.baseFee); err == nil {
+ if wrapped, err := newTxWithMinerFee(txs[0], acc, t.baseFee, t.exchangeRates); err == nil {
t.heads[0], t.txs[acc] = wrapped, txs[1:]
heap.Fix(&t.heads, 0)
return
diff --git op-geth/miner/ordering_test.go Celo/miner/ordering_test.go
index 3587a835c8844d3ed6b2a19a45e7aa18ba48f557..be1dd349fe07fdd7b4e7227289d15f1425d839c6 100644
--- op-geth/miner/ordering_test.go
+++ Celo/miner/ordering_test.go
@@ -102,7 +102,7 @@ }
expectedCount += count
}
// Sort the transactions and cross check the nonce ordering
- txset := newTransactionsByPriceAndNonce(signer, groups, baseFee)
+ txset := newTransactionsByPriceAndNonce(signer, groups, baseFee, nil)
txs := types.Transactions{}
for tx, _ := txset.Peek(); tx != nil; tx, _ = txset.Peek() {
@@ -168,7 +168,7 @@ BlobGas: tx.BlobGas(),
})
}
// Sort the transactions and cross check the nonce ordering
- txset := newTransactionsByPriceAndNonce(signer, groups, nil)
+ txset := newTransactionsByPriceAndNonce(signer, groups, nil, nil)
txs := types.Transactions{}
for tx, _ := txset.Peek(); tx != nil; tx, _ = txset.Peek() {
@@ -194,3 +194,55 @@ }
}
}
}
+
+func TestCorrectTransactionSortWithFeeCurrency(t *testing.T) {
+ t.Parallel()
+
+ user1, _ := crypto.GenerateKey()
+ user2, _ := crypto.GenerateKey()
+ signer := types.LatestSignerForChainID(big.NewInt(1))
+
+ rates := make(common.ExchangeRates)
+ feeCurrency := common.BigToAddress(common.Big1)
+ rates[feeCurrency] = big.NewRat(10, 1)
+
+ groups := map[common.Address][]*txpool.LazyTransaction{}
+
+ baseFee := big.NewInt(10)
+
+ addr1 := crypto.PubkeyToAddress(user1.PublicKey)
+ user1GasFeeCap := uint256.NewInt(950)
+ // user1 pays 950 feeCurrency, rate is 10/1, baseFee is 10 in feeCurrency is 100,
+ // 950 - 100 = 850 feeCurrency tip => 85 celo tip
+ // without converting the tip (error fixed): 950 - 10 = 940 feeCurrency tip => 94 celo tip (this test fails)
+ groups[addr1] = append(groups[addr1], &txpool.LazyTransaction{
+ GasFeeCap: user1GasFeeCap,
+ GasTipCap: user1GasFeeCap,
+ Gas: 100,
+ FeeCurrency: &feeCurrency,
+ })
+
+ addr2 := crypto.PubkeyToAddress(user2.PublicKey)
+ user2GasFeeCap := uint256.NewInt(100)
+ // user2 pays 100 celos, baseFee is 10, tip is 90
+ groups[addr2] = append(groups[addr2], &txpool.LazyTransaction{
+ GasFeeCap: user2GasFeeCap,
+ GasTipCap: user2GasFeeCap,
+ Gas: 100,
+ })
+
+ // Sort the transactions and cross check the nonce ordering
+ txset := newTransactionsByPriceAndNonce(signer, groups, baseFee, rates)
+
+ var auxTx *txpool.LazyTransaction
+ var fees *uint256.Int
+ auxTx, fees = txset.Peek()
+ if auxTx.FeeCurrency != nil || auxTx.GasFeeCap.Cmp(user2GasFeeCap) != 0 || fees.Cmp(uint256.NewInt(90)) != 0 {
+ t.Error("expected tx from user2, got the tx from user1")
+ }
+ txset.Shift()
+ auxTx, fees = txset.Peek()
+ if auxTx.FeeCurrency == nil || *auxTx.FeeCurrency != feeCurrency || auxTx.GasFeeCap.Cmp(user1GasFeeCap) != 0 || fees.Cmp(uint256.NewInt(85)) != 0 {
+ t.Error("expected tx from user1, got the tx from user2")
+ }
+}
diff --git op-geth/miner/worker.go Celo/miner/worker.go
index a4f297c6c6b453d7558bdfdbe427413327bd2543..ef8844c6c54a27db96e5b73a114587e68f0c12f3 100644
--- op-geth/miner/worker.go
+++ Celo/miner/worker.go
@@ -28,6 +28,7 @@ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
+ "github.com/ethereum/go-ethereum/contracts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
@@ -81,6 +82,11 @@ witness *stateless.Witness
noTxs bool // true if we are reproducing a block, and do not have to check interop txs
rpcCtx context.Context // context to control block-building RPC work. No RPC allowed if nil.
+
+ // Celo specific
+ multiGasPool *core.MultiGasPool // available per-fee-currency gas used to pack transactions
+ feeCurrencyAllowlist common.AddressSet
+ feeCurrencyContext *common.FeeCurrencyContext
}
const (
@@ -141,6 +147,14 @@ gasLimit = effectiveGasLimit
}
}
work.gasPool = new(core.GasPool).AddGas(gasLimit)
+ }
+ if work.multiGasPool == nil {
+ work.multiGasPool = core.NewMultiGasPool(
+ work.header.GasLimit,
+ work.feeCurrencyAllowlist,
+ miner.config.FeeCurrencyDefault,
+ miner.config.FeeCurrencyLimits,
+ )
}
misc.EnsureCreate2Deployer(miner.chainConfig, work.header.Time, work.state)
@@ -152,6 +166,10 @@ err = miner.commitTransaction(work, tx)
if err != nil {
return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)}
}
+ // the non-fee currency pool in the multipool is not used, but for consistency
+ // subtract the gas. Don't check the error either, this has been checked already
+ // with the work.gasPool.
+ work.multiGasPool.PoolFor(nil).SubGas(tx.Gas())
}
if !params.noTxs {
// use shared interrupt if present
@@ -301,13 +319,25 @@ log.Error("Failed to create sealing context", "err", err)
return nil, err
}
env.noTxs = genParams.noTxs
+ context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state, env.feeCurrencyContext)
+ if evicted := miner.feeCurrencyBlocklist.Evict(parent); len(evicted) > 0 {
+ log.Warn(
+ "Evicted temporarily blocked fee-currencies from local block-list",
+ "evicted-fee-currencies", evicted,
+ "eviction-timeout-seconds", EvictionTimeoutSeconds,
+ )
+ }
+ env.feeCurrencyAllowlist = miner.feeCurrencyBlocklist.FilterAllowlist(
+ common.CurrencyAllowlist(env.feeCurrencyContext.ExchangeRates),
+ header,
+ )
+
if header.ParentBeaconRoot != nil {
- context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state)
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
}
if miner.chainConfig.IsPrague(header.Number, header.Time) {
- context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state)
+ context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state, env.feeCurrencyContext)
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
core.ProcessParentBlockHash(header.ParentHash, vmenv, env.state)
}
@@ -333,6 +363,7 @@ state = state.Copy()
release()
}
}
+ feeCurrencyContext := core.GetFeeCurrencyContext(header, miner.chainConfig, state)
if witness {
bundle, err := stateless.NewWitness(header, miner.chain)
@@ -349,6 +380,8 @@ coinbase: coinbase,
header: header,
witness: state.Witness(),
rpcCtx: rpcCtx,
+
+ feeCurrencyContext: feeCurrencyContext,
}, nil
}
@@ -372,6 +405,15 @@ }
receipt, err := miner.applyTransaction(env, tx)
if err != nil {
+ if errors.Is(err, contracts.ErrFeeCurrencyEVMCall) {
+ log.Warn(
+ "fee-currency EVM execution error, temporarily blocking fee-currency in local txpools",
+ "tx-hash", tx.Hash(),
+ "fee-currency", tx.FeeCurrency(),
+ "error", err.Error(),
+ )
+ miner.blockFeeCurrency(env, *tx.FeeCurrency(), err)
+ }
return err
}
env.txs = append(env.txs, tx)
@@ -435,7 +477,7 @@ return miner.checkInterop(env.rpcCtx, tx, result.Failed(), logs)
},
}
}
- receipt, err := core.ApplyTransactionExtended(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{}, extraOpts)
+ receipt, err := core.ApplyTransactionExtended(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{}, extraOpts, env.feeCurrencyContext)
if err != nil {
env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp)
@@ -485,6 +527,14 @@ if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit)
}
blockDABytes := new(big.Int)
+ if env.multiGasPool == nil {
+ env.multiGasPool = core.NewMultiGasPool(
+ env.header.GasLimit,
+ env.feeCurrencyAllowlist,
+ miner.config.FeeCurrencyDefault,
+ miner.config.FeeCurrencyLimits,
+ )
+ }
for {
// Check interruption signal and abort building if it's fired.
if interrupt != nil {
@@ -527,6 +577,13 @@ }
if ltx == nil {
break
}
+ if ltx.FeeCurrency != nil {
+ if _, ok := env.feeCurrencyAllowlist[*ltx.FeeCurrency]; !ok {
+ log.Trace("Fee-currency not in local allowlist", "hash", ltx.Hash, "fee-currency", ltx.FeeCurrency)
+ txs.Pop()
+ continue
+ }
+ }
// If we don't have enough space for the next transaction, skip the account.
if env.gasPool.Gas() < ltx.Gas {
log.Trace("Not enough gas left for transaction", "hash", ltx.Hash, "left", env.gasPool.Gas(), "needed", ltx.Gas)
@@ -548,6 +605,15 @@ txs.Pop()
continue
}
}
+ if left := env.multiGasPool.PoolFor(ltx.FeeCurrency).Gas(); left < ltx.Gas {
+ log.Trace(
+ "Not enough specific fee-currency gas left for transaction",
+ "currency", ltx.FeeCurrency, "hash", ltx.Hash,
+ "left", left, "needed", ltx.Gas,
+ )
+ txs.Pop()
+ continue
+ }
// Transaction seems to fit, pull it up from the pool
tx := ltx.Resolve()
if tx == nil {
@@ -569,7 +635,9 @@ }
// Start executing the transaction
env.state.SetTxContext(tx.Hash(), env.tcount)
+ availableGas := env.gasPool.Gas()
err := miner.commitTransaction(env, tx)
+ gasUsed := availableGas - env.gasPool.Gas()
switch {
case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift
@@ -594,6 +662,23 @@ log.Warn("Transaction was rejected during block-building", "hash", ltx.Hash, "err", err)
txs.Pop()
case errors.Is(err, nil):
+ err := env.multiGasPool.PoolFor(tx.FeeCurrency()).SubGas(gasUsed)
+ if err != nil {
+ // Should never happen as we check it above
+ log.Warn(
+ "Unexpectedly reached limit for fee currency, but tx will not be skipped",
+ "hash", tx.Hash(), "gas", env.multiGasPool.PoolFor(tx.FeeCurrency()).Gas(),
+ "tx gas used", gasUsed,
+ )
+ // If we reach this codepath, we want to still include the transaction,
+ // since the "global" gasPool in the commitTransaction accepted it and we
+ // would have to roll the transaction back now, introducing unnecessary
+ // complexity.
+ // Since we shouldn't reach this point anyways and the
+ // block gas limit per fee currency is enforced voluntarily and not
+ // included in the consensus this is fine.
+ }
+
// Everything ok, collect the logs and shift in the next transaction from the same account
blockDABytes = daBytesAfter
txs.Shift()
@@ -649,16 +734,16 @@ }
}
// Fill the block with all available pending transactions.
if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 {
- plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee)
- blobTxs := newTransactionsByPriceAndNonce(env.signer, localBlobTxs, env.header.BaseFee)
+ plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee, env.feeCurrencyContext.ExchangeRates)
+ blobTxs := newTransactionsByPriceAndNonce(env.signer, localBlobTxs, env.header.BaseFee, env.feeCurrencyContext.ExchangeRates)
if err := miner.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil {
return err
}
}
if len(remotePlainTxs) > 0 || len(remoteBlobTxs) > 0 {
- plainTxs := newTransactionsByPriceAndNonce(env.signer, remotePlainTxs, env.header.BaseFee)
- blobTxs := newTransactionsByPriceAndNonce(env.signer, remoteBlobTxs, env.header.BaseFee)
+ plainTxs := newTransactionsByPriceAndNonce(env.signer, remotePlainTxs, env.header.BaseFee, env.feeCurrencyContext.ExchangeRates)
+ blobTxs := newTransactionsByPriceAndNonce(env.signer, remoteBlobTxs, env.header.BaseFee, env.feeCurrencyContext.ExchangeRates)
if err := miner.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil {
return err
@@ -725,3 +810,15 @@ blockTime = 2
}
return time.Duration(blockTime) * time.Second, nil
}
+
+func (miner *Miner) blockFeeCurrency(env *environment, feeCurrency common.Address, err error) {
+ // the fee-currency is still in the allowlist of this environment,
+ // so set the fee-currency block gas limit to 0 to prevent other
+ // transactions.
+ pool := env.multiGasPool.PoolFor(&feeCurrency)
+ pool.SetGas(0)
+ // also add the fee-currency to a worker-wide blocklist,
+ // so that they are not allowlisted in the following blocks
+ // (only locally in the txpool, not consensus-critical)
+ miner.feeCurrencyBlocklist.Add(feeCurrency, *env.header)
+}
diff --git op-geth/params/celo.go Celo/params/celo.go
new file mode 100644
index 0000000000000000000000000000000000000000..a6b053bd06f8f5718569c6f0f550640921409dff
--- /dev/null
+++ Celo/params/celo.go
@@ -0,0 +1,5 @@
+package params
+
+const (
+ DefaultGasLimit uint64 = 20000000 // Gas limit of the blocks before BlockchainParams contract is loaded.
+)
diff --git op-geth/params/celo_config.go Celo/params/celo_config.go
new file mode 100644
index 0000000000000000000000000000000000000000..c761cb46058d42c4221fcfecd4141a63e69ecb6e
--- /dev/null
+++ Celo/params/celo_config.go
@@ -0,0 +1,7 @@
+package params
+
+const (
+ CeloMainnetChainID = 42220
+ CeloBaklavaChainID = 62320
+ CeloAlfajoresChainID = 44787
+)
diff --git op-geth/params/config_test.go Celo/params/config_test.go
index 4e5f3e5af416b27b3fbed26351f68da4907bd646..09b89f2a9498a4eea1e6087262e912a515feb65b 100644
--- op-geth/params/config_test.go
+++ Celo/params/config_test.go
@@ -211,3 +211,22 @@ if r := c.Rules(big.NewInt(0), true, stamp); !r.IsOptimismRegolith {
t.Errorf("expected %v to be regolith", stamp)
}
}
+
+func TestConfigRulesCel2(t *testing.T) {
+ c := &ChainConfig{
+ Cel2Time: newUint64(500),
+ Optimism: &OptimismConfig{},
+ }
+ var stamp uint64
+ if r := c.Rules(big.NewInt(0), true, stamp); r.IsCel2 {
+ t.Errorf("expected %v to not be Cel2", stamp)
+ }
+ stamp = 500
+ if r := c.Rules(big.NewInt(0), true, stamp); !r.IsCel2 {
+ t.Errorf("expected %v to be Cel2", stamp)
+ }
+ stamp = math.MaxInt64
+ if r := c.Rules(big.NewInt(0), true, stamp); !r.IsCel2 {
+ t.Errorf("expected %v to be Cel2", stamp)
+ }
+}
diff --git op-geth/rlp/celo_raw.go Celo/rlp/celo_raw.go
new file mode 100644
index 0000000000000000000000000000000000000000..de024d3f5de6f9c179f02de335382f6aae83699d
--- /dev/null
+++ Celo/rlp/celo_raw.go
@@ -0,0 +1,16 @@
+package rlp
+
+// ReadNext reads the next RLP item from the given buffer and returns the remaining buffer,
+// the kind of the item, the size of the tag, the size of the content, and any error encountered.
+func ReadNext(buf []byte) (remaining []byte, kind Kind, tagsize, contentsize uint64, err error) {
+ remaining = buf
+ kind, tagsize, contentsize, err = readKind(remaining)
+ if err != nil {
+ return nil, 0, 0, 0, err
+ }
+ remaining = remaining[tagsize:]
+ if kind != List {
+ remaining = remaining[contentsize:]
+ }
+ return
+}
diff --git op-geth/tests/block_test.go Celo/tests/block_test.go
index 4bbd2571d73b6fadb66a13d1c52fbcc1b28fe58c..35ca570dddaa20aee89b770882bcd3954dde1dc6 100644
--- op-geth/tests/block_test.go
+++ Celo/tests/block_test.go
@@ -18,6 +18,7 @@ package tests
import (
"math/rand"
+ "regexp"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -76,6 +77,13 @@ }
bt := new(testMatcher)
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
+ matches, err := regexp.MatchString("blockchain_test-create2?-over_limit_(ones|zeros)", name)
+ if err != nil {
+ t.Errorf("Bad regexp: %s", err)
+ }
+ if matches {
+ t.Skipf("Celo has increased the MaxCodeSize, which makes some tests invalid")
+ }
execBlockTest(t, bt, test)
})
}
diff --git op-geth/tests/state_test.go Celo/tests/state_test.go
index 95233c3b103273599a3a55e167c8c7113b7a7d22..dd77c7e021253a525ef8205a996a5d08cd6ccf28 100644
--- op-geth/tests/state_test.go
+++ Celo/tests/state_test.go
@@ -25,6 +25,7 @@ "math/rand"
"os"
"path/filepath"
"reflect"
+ "regexp"
"strings"
"testing"
"time"
@@ -98,6 +99,13 @@ }
st := new(testMatcher)
st.walk(t, executionSpecStateTestDir, func(t *testing.T, name string, test *StateTest) {
+ matches, err := regexp.MatchString("state_test-(create2?-)?over_limit_(ones|zeros)", name)
+ if err != nil {
+ t.Errorf("Bad regexp: %s", err)
+ }
+ if matches {
+ t.Skipf("Celo has increased the MaxCodeSize, which makes some tests invalid")
+ }
execStateTest(t, st, test)
})
}
@@ -302,7 +310,8 @@ }
// Prepare the EVM.
txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config, state.StateDB)
+ feeCurrencyContext := core.GetFeeCurrencyContext(block.Header(), config, state.StateDB)
+ context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config, state.StateDB, feeCurrencyContext)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig)
diff --git op-geth/tests/state_test_util.go Celo/tests/state_test_util.go
index 76c4e54055dc8a721c79b3396bd31c6de705732e..1dd9da48adf519a66ecfd7fcb2d6381efd1b68f4 100644
--- op-geth/tests/state_test_util.go
+++ Celo/tests/state_test_util.go
@@ -278,7 +278,8 @@ }
// Prepare the EVM.
txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config, st.StateDB)
+ feeCurrencyContext := core.GetFeeCurrencyContext(block.Header(), config, st.StateDB)
+ context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config, st.StateDB, feeCurrencyContext)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
context.Random = nil
diff --git op-geth/tests/transaction_test_util.go Celo/tests/transaction_test_util.go
index d3dbbd5db294065590a65c7518338abd13e6beee..57b1f7db2239a722cde7fc3db5dbed2874e4a2e5 100644
--- op-geth/tests/transaction_test_util.go
+++ Celo/tests/transaction_test_util.go
@@ -59,7 +59,7 @@ if err != nil {
return nil, nil, err
}
// Intrinsic gas
- requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false)
+ requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false, nil, common.IntrinsicGasCosts{})
if err != nil {
return nil, nil, err
}
Ignored changes
+738
-392
diff --git op-geth/.circleci/check-releases.sh Celo/.circleci/check-releases.sh
deleted file mode 100755
index f3595e1320377bea4b17c13326d87342083071c0..0000000000000000000000000000000000000000
--- op-geth/.circleci/check-releases.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-LATEST_RELEASE=$(curl -s --fail -L \
- -H "Accept: application/vnd.github+json" \
- -H "X-GitHub-Api-Version: 2022-11-28" \
- https://api.github.com/repos/ethereum/go-ethereum/releases \
- | jq -r '(.[] | select(.draft==false) | select(.prerelease==false)).tag_name' | head -n 1)
-
-echo "Detected latest go-ethereum release as ${LATEST_RELEASE}"
-
-git remote add upstream https://github.com/ethereum/go-ethereum
-git fetch upstream > /dev/null
-
-if git branch --contains "${LATEST_RELEASE}" 2>&1 | grep -e '^[ *]*optimism$' > /dev/null
-then
- echo "Up to date with latest release. Great job! 🎉"
-else
- echo "Release has not been merged"
- exit 1
-fi
diff --git op-geth/.circleci/ci-docker-tag-op-geth-release.sh Celo/.circleci/ci-docker-tag-op-geth-release.sh
deleted file mode 100755
index 7b66e789a4d7e50699f2d22ff037117c0d7a3a35..0000000000000000000000000000000000000000
--- op-geth/.circleci/ci-docker-tag-op-geth-release.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env bash
-
-set -euo pipefail
-
-DOCKER_REPO=$1
-GIT_TAG=$2
-GIT_SHA=$3
-
-IMAGE_NAME="op-geth"
-IMAGE_TAG=$GIT_TAG
-
-SOURCE_IMAGE_TAG="$DOCKER_REPO/$IMAGE_NAME:$GIT_SHA"
-TARGET_IMAGE_TAG="$DOCKER_REPO/$IMAGE_NAME:$IMAGE_TAG"
-TARGET_IMAGE_TAG_LATEST="$DOCKER_REPO/$IMAGE_NAME:latest"
-
-echo "Checking if docker images exist for '$IMAGE_NAME'"
-echo ""
-tags=$(gcloud container images list-tags "$DOCKER_REPO/$IMAGE_NAME" --limit 1 --format json)
-if [ "$tags" = "[]" ]; then
- echo "No existing docker images were found for '$IMAGE_NAME'. The code tagged with '$GIT_TAG' may not have an associated dockerfile or docker build job."
- echo "If this service has a dockerfile, add a docker-publish job for it in the circleci config."
- echo ""
- echo "Exiting"
- exit 0
-fi
-
-echo "Tagging $SOURCE_IMAGE_TAG with '$IMAGE_TAG'"
-gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG"
-
-# Do not tag with latest if the release is a release candidate.
-if [[ "$IMAGE_TAG" == *"rc"* ]]; then
- echo "Not tagging with 'latest' because the release is a release candidate."
- exit 0
-fi
-
-echo "Tagging $SOURCE_IMAGE_TAG with 'latest'"
-gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG_LATEST"
-
diff --git op-geth/.github/renovate.json Celo/.github/renovate.json
new file mode 100644
index 0000000000000000000000000000000000000000..f19d8969ae48ed1dd6445c14ff9bfebf1de9ca26
--- /dev/null
+++ Celo/.github/renovate.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "local>celo-org/.github:renovate-config"
+ ],
+ "packageRules": [
+ {
+ "enabled": false,
+ "groupName": "everything",
+ "matchManagers": ["gomod", "dockerfile", "docker-compose", "npm", "html", "github-actions", "pip_requirements"],
+ "matchPackagePatterns": ["*"],
+ "separateMajorMinor": false
+ }
+ ],
+ "vulnerabilityAlerts": {
+ "enabled": true
+ }
+}
diff --git op-geth/.github/workflows/ci.yaml Celo/.github/workflows/ci.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fa5150581fe6d91558c63dd30ae83f75223f4a12
--- /dev/null
+++ Celo/.github/workflows/ci.yaml
@@ -0,0 +1,82 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ - celo*
+
+ pull_request:
+ branches:
+ - master
+ - celo*
+
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ Test:
+ runs-on: ["8-cpu","self-hosted","org"]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.22'
+
+ - name: Build
+ run: make all
+
+ - name: Test
+ run: make test
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly-143abd6a768eeb52a5785240b763d72a56987b4a
+
+ - name: Run e2e tests local
+ shell: bash
+ run: e2e_test/run_all_tests.sh
+
+ Lint:
+ runs-on: ["8-cpu","self-hosted","org"]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.22'
+ cache: false
+
+ - name: Lint
+ run: make lint
+
+ Generate:
+ runs-on: ["ubuntu-latest"]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.22'
+ cache: false
+
+ - name: Generate check
+ # Run go generate and see if anything changed.
+ # We need to use || true because currently we do not install protoc
+ # and protoc-gen-go which are required for generating some files,
+ # however despite this the non protobuf related types are generated.
+ run: |
+ go generate ./... || true
+ if [ -n "$(git status --porcelain)" ]; then
+ echo "Changes detected after running 'go generate ./...' run generate locally and commit the changes."
+ exit 1
+ fi
+ shell: sh
diff --git op-geth/.github/workflows/docker-build-scan.yaml Celo/.github/workflows/docker-build-scan.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c21f35f8bd4ee14046510e064d41a75cd778b140
--- /dev/null
+++ Celo/.github/workflows/docker-build-scan.yaml
@@ -0,0 +1,61 @@
+name: Docker Build Push
+on:
+ push:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+permissions:
+ contents: read
+
+jobs:
+ build-scan-container-geth:
+ runs-on: ['self-hosted', 'org', '8-cpu']
+ permissions:
+ contents: read
+ security-events: write
+ id-token: write
+ steps:
+ - uses: actions/checkout@v4
+ - name: Login at GCP Artifact Registry
+ uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0.5
+ with:
+ workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-op-geth/providers/github-by-repos
+ service-account: op-geth-dev@blockchaintestsglobaltestnet.iam.gserviceaccount.com
+ docker-gcp-registries: us-west1-docker.pkg.dev
+ - name: Build and push container
+ uses: celo-org/reusable-workflows/.github/actions/build-container@v2.0.5
+ with:
+ platforms: ${{ startsWith(github.ref, 'refs/heads/celo') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
+ registry: us-west1-docker.pkg.dev/blockchaintestsglobaltestnet/dev-images/op-geth
+ tags: ${{ github.sha }}
+ context: .
+ dockerfile: Dockerfile
+ push: true
+ trivy: ${{ startsWith(github.ref, 'refs/heads/celo') }}
+
+ build-scan-container-bootnode:
+ runs-on: ['self-hosted', 'org', '8-cpu']
+ permissions:
+ contents: read
+ security-events: write
+ id-token: write
+ steps:
+ - uses: actions/checkout@v4
+ - name: Login at GCP Artifact Registry
+ uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0.5
+ with:
+ workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-op-geth/providers/github-by-repos
+ service-account: op-geth-dev@blockchaintestsglobaltestnet.iam.gserviceaccount.com
+ docker-gcp-registries: us-west1-docker.pkg.dev
+ - name: Build and push container
+ uses: celo-org/reusable-workflows/.github/actions/build-container@v2.0.5
+ with:
+ platforms: ${{ startsWith(github.ref, 'refs/heads/celo') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
+ registry: us-west1-docker.pkg.dev/blockchaintestsglobaltestnet/dev-images/op-geth-bootnode
+ tags: ${{ github.sha }}
+ context: .
+ dockerfile: Dockerfile.bootnode
+ push: true
+ trivy: ${{ startsWith(github.ref, 'refs/heads/celo') }}
diff --git op-geth/.github/workflows/e2e-test-deployed-network.yaml Celo/.github/workflows/e2e-test-deployed-network.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6bf14d01271996db7f20c101c33bf4cb022d34f7
--- /dev/null
+++ Celo/.github/workflows/e2e-test-deployed-network.yaml
@@ -0,0 +1,42 @@
+name: e2e-test-deployed-network
+
+on:
+ schedule:
+ - cron: "0 14 * * *"
+ pull_request:
+ branches:
+ - master
+ - celo*
+ paths:
+ - 'e2e_test/**'
+
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ e2e-tests:
+ runs-on: ["8-cpu","self-hosted","org"]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.21'
+
+ - name: Build
+ run: make all
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly-143abd6a768eeb52a5785240b763d72a56987b4a
+
+ - name: Run e2e tests alfajores
+ shell: bash
+ run: NETWORK=alfajores e2e_test/run_all_tests.sh
diff --git op-geth/.github/workflows/go.yml Celo/.github/workflows/go.yml
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
Binary files op-geth/.github/workflows/go.yml and /dev/null differ
diff --git op-geth/.github/workflows/pages.yaml Celo/.github/workflows/pages.yaml
index 3c45b5d3390e8fa379824c7001f308ad721a2f86..802e7b25ad2a105376cb760cec12e93a202338c1 100644
--- op-geth/.github/workflows/pages.yaml
+++ Celo/.github/workflows/pages.yaml
@@ -1,19 +1,25 @@
name: Build and publish forkdiff github-pages
-permissions:
- contents: write
on:
push:
branches:
- - optimism
+ - celo[0-9]+
+
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
jobs:
deploy:
+ permissions:
+ contents: write
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
- fetch-depth: 1000 # make sure to fetch the old commit we diff against
+ fetch-depth: 0 # make sure to fetch the old commit we diff against
- name: Build forkdiff
uses: "docker://protolambda/forkdiff:0.1.0"
@@ -25,12 +31,9 @@ run: |
mkdir -p tmp/pages
mv index.html tmp/pages/index.html
touch tmp/pages/.nojekyll
- if [ "$GITHUB_REPOSITORY" == "ethereum-optimism/op-geth" ]; then
- echo "op-geth.optimism.io" > tmp/pages/CNAME
- fi;
- name: Deploy
- uses: JamesIves/github-pages-deploy-action@v4
+ uses: JamesIves/github-pages-deploy-action@920cbb300dcd3f0568dbc42700c61e2fd9e6139c
with:
folder: tmp/pages
clean: true
diff --git op-geth/.github/workflows/pr.yaml Celo/.github/workflows/pr.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..67339e9644a21940461606dce44b15f69e23e9cc
--- /dev/null
+++ Celo/.github/workflows/pr.yaml
@@ -0,0 +1,20 @@
+name: PR only
+
+on:
+ pull_request:
+ branches:
+ - master
+ - celo*
+
+permissions:
+ contents: read
+
+jobs:
+ dependencies:
+ concurrency: ci-${{ github.ref }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: "Dependency Review"
+ uses: actions/dependency-review-action@v4
+ with:
+ warn-only: true
diff --git op-geth/.github/workflows/scorecard.yml Celo/.github/workflows/scorecard.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7ecd80e85ea2680d66e0540154490cd7ced2a879
--- /dev/null
+++ Celo/.github/workflows/scorecard.yml
@@ -0,0 +1,73 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecard supply-chain security
+on:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: '42 12 * * 4'
+ push:
+ branches: [ "celo8" ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ # contents: read
+ # actions: read
+
+ steps:
+ - name: "Checkout code"
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
+ # - you want to enable the Branch-Protection check on a *public* repository, or
+ # - you are installing Scorecard on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
+ # repo_token: ${{ secrets.SCORECARD_TOKEN }}
+
+ # Public repositories:
+ # - Publish results to OpenSSF REST API for easy access by consumers
+ # - Allows the repository to include the Scorecard badge.
+ # - See https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories:
+ # - `publish_results` will always be set to `false`, regardless
+ # of the value entered here.
+ publish_results: true
+
+ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+ # format to the repository Actions tab.
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@v4
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard (optional).
+ # Commenting out will disable upload of results to your repo's Code Scanning dashboard
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: results.sarif
diff --git op-geth/Makefile Celo/Makefile
index 9e8b9569cd613c61d56e9ce9a0fd124fd1532c61..6ef81c18cb79da32a6df9eeabddd2ed895fade99 100644
--- op-geth/Makefile
+++ Celo/Makefile
@@ -50,6 +50,7 @@
forkdiff:
docker run --rm \
--mount src=$(shell pwd),target=/host-pwd,type=bind \
+ --platform linux/amd64 \
protolambda/forkdiff:latest \
-repo /host-pwd/ -fork /host-pwd/fork.yaml -out /host-pwd/forkdiff.html
diff --git op-geth/eth/tracers/internal/tracetest/testdata/call_tracer/fee_currency.json Celo/eth/tracers/internal/tracetest/testdata/call_tracer/fee_currency.json
new file mode 100644
index 0000000000000000000000000000000000000000..4c9fde0dee995365d867439cc8d0eea62aa60d38
--- /dev/null
+++ Celo/eth/tracers/internal/tracetest/testdata/call_tracer/fee_currency.json
@@ -0,0 +1,84 @@
+{
+ "genesis": {
+ "difficulty": "0",
+ "extraData": "0xd8820100846765746888676f312e32312e368664617277696e",
+ "gasLimit": "11533720",
+ "hash": "0x64579a9548b63477d50175fe20108afda5ffcbc38d7ff709dd2f07cae0077bdb",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "mixHash": "0xa5bdfbdd8f30e8eb34108e78f94274cd60dd348aed7428a47330f94e6d8f0378",
+ "nonce": "0x0000000000000000",
+ "number": "3",
+ "stateRoot": "0xe7ce9d40973a218b36332fcfd42b102e1b1f620a914f344de0f996b429e26b2c",
+ "timestamp": "1721119176",
+ "totalDifficulty": "1",
+ "withdrawals": [],
+ "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "alloc": {
+ "0x0000000000000000000000000000000000000000": {
+ "balance": "0xa410",
+ "nonce": "1"
+ },
+ "0x000000000000000000000000000000000000ce16": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806358cf96721161008c57806395d89b411161006657806395d89b41146101ca578063a457c2d7146101d2578063a9059cbb146101e5578063dd62ed3e146101f857600080fd5b806358cf96721461016c5780636a30b2531461018157806370a082311461019457600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015957600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f99190610c15565b60405180910390f35b610115610110366004610cb1565b6102d0565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610cdb565b6102e8565b604051601281526020016100f9565b610115610167366004610cb1565b61030e565b61017f61017a366004610cb1565b61035a565b005b61017f61018f366004610d17565b61041e565b6101296101a2366004610d8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6100ec610510565b6101156101e0366004610cb1565b61051f565b6101156101f3366004610cb1565b6105fb565b610129610206366004610daa565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461024d90610ddd565b80601f016020809104026020016040519081016040528092919081815260200182805461027990610ddd565b80156102c65780601f1061029b576101008083540402835291602001916102c6565b820191906000526020600020905b8154815290600101906020018083116102a957829003601f168201915b5050505050905090565b6000336102de818585610609565b5060019392505050565b6000336102f68582856107bc565b610301858585610893565b60019150505b9392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906102de9082908690610355908790610e5f565b610609565b33156103c7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906103fc908490610e77565b9250508190555080600260008282546104159190610e77565b90915550505050565b3315610486576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064016103be565b73ffffffffffffffffffffffffffffffffffffffff8816600090815260208190526040812080548692906104bb908490610e5f565b909155506104cc9050888683610b46565b6104d69085610e5f565b93506104e3888885610b46565b6104ed9085610e5f565b935083600260008282546105019190610e5f565b90915550505050505050505050565b60606004805461024d90610ddd565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909190838110156105e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016103be565b6105f08286868403610609565b506001949350505050565b6000336102de818585610893565b73ffffffffffffffffffffffffffffffffffffffff83166106ab576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff821661074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461088d5781811015610880576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103be565b61088d8484848403610609565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610936576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff82166109d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220858503905591851681529081208054849290610ad3908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b3991815260200190565b60405180910390a361088d565b600073ffffffffffffffffffffffffffffffffffffffff8316610b6b57506000610307565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208054849290610ba0908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c0691815260200190565b60405180910390a35092915050565b600060208083528351808285015260005b81811015610c4257858101830151858201604001528201610c26565b81811115610c54576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c88565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c88565b9250610d0760208501610c88565b9150604084013590509250925092565b600080600080600080600080610100898b031215610d3457600080fd5b610d3d89610c88565b9750610d4b60208a01610c88565b9650610d5960408a01610c88565b9550610d6760608a01610c88565b979a969950949760808101359660a0820135965060c0820135955060e0909101359350915050565b600060208284031215610da157600080fd5b61030782610c88565b60008060408385031215610dbd57600080fd5b610dc683610c88565b9150610dd460208401610c88565b90509250929050565b600181811c90821680610df157607f821691505b602082108103610e2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610e7257610e72610e30565b500190565b600082821015610e8957610e89610e30565b50039056fea164736f6c634300080f000a",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000",
+ "0x85de51d5625304e72107ba46e4bf731a09f455f609080a043a4ca2c0db099937": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
+ }
+ },
+ "0x00000000000000000000000000000000deadbeef": {
+ "balance": "0x2"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc787f65890fb62",
+ "nonce": "3"
+ }
+ },
+ "config": {
+ "chainId": 1337,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "muirGlacierBlock": 0,
+ "berlinBlock": 0,
+ "londonBlock": 0,
+ "arrowGlacierBlock": 0,
+ "grayGlacierBlock": 0,
+ "shanghaiTime": 0,
+ "cel2Time": 0,
+ "terminalTotalDifficulty": 0,
+ "terminalTotalDifficultyPassed": true
+ }
+ },
+ "context": {
+ "baseFeePerGas": "670833297",
+ "number": "4",
+ "difficulty": "0",
+ "timestamp": "1721119177",
+ "gasLimit": "11544982",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "feeCurrencyContext": {
+ "exchangeRates": {
+ "0x000000000000000000000000000000000000cE16": [2, 1]
+ },
+ "intrinsicGasCosts": {
+ "0x000000000000000000000000000000000000cE16": 50000
+ }
+ }
+ },
+ "input": "0x7bf87e8205390380847735940083015f909400000000000000000000000000000000deadbeef0280c094000000000000000000000000000000000000ce1680a06b175ce301873e64da4ef5c2fa5c8f6eb61b9ab1d7bfbe103812a32ad94a5c4fa05d55f5f23e76ce11491f4776299a1a65d4af48924b330a7681e394b2ef8e173b",
+ "result": {
+ "from": "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a",
+ "gas": "0x15f90",
+ "gasUsed": "0x11558",
+ "to": "0x00000000000000000000000000000000deadbeef",
+ "input": "0x",
+ "value": "0x2",
+ "type": "CALL"
+ }
+}
diff --git op-geth/eth/tracers/internal/tracetest/testdata/call_tracer/transfer_precompile.json Celo/eth/tracers/internal/tracetest/testdata/call_tracer/transfer_precompile.json
new file mode 100644
index 0000000000000000000000000000000000000000..de1ca1600ca8f4ea203fe7eb020e40bf6b7afb20
--- /dev/null
+++ Celo/eth/tracers/internal/tracetest/testdata/call_tracer/transfer_precompile.json
@@ -0,0 +1,83 @@
+{
+ "genesis": {
+ "difficulty": "0",
+ "extraData": "0xd8820100846765746888676f312e32312e368664617277696e",
+ "gasLimit": "11511229",
+ "hash": "0x5d605116d6623291269abed0d1f35e1e3bb04dd44e2039007e3111ca0cef1624",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "mixHash": "0x5e9f72a9c84605a6ac33f5b5c603a7ce89c63be3f9b9de3072beb167f0062131",
+ "nonce": "0x0000000000000000",
+ "number": "1",
+ "stateRoot": "0x72c0d1653648a020479291917e2fedce749162b54a6d33962481f58815d2537f",
+ "timestamp": "1721214738",
+ "totalDifficulty": "1",
+ "withdrawals": [],
+ "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "alloc": {
+ "0x0000000000000000000000000000000000000000": {
+ "balance": "0x0"
+ },
+ "0x00000000000000000000000000000000000000fd": {
+ "balance": "0x0"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc74344293a6d9c",
+ "nonce": "1"
+ },
+ "0x471ece3750da237f93b8e339c536989b8978a438": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106101fb5760003560e01c80637b1039991161011a578063a91ee0dc116100ad578063d4d83cfb1161007c578063d4d83cfb14610565578063db2b4d101461056d578063dd62ed3e14610593578063e1d6aceb146105c1578063f2fde38b14610646576101fb565b8063a91ee0dc146104f4578063b921e1631461051a578063c4d66de814610537578063c80ec5221461055d576101fb565b80639358928b116100e95780639358928b1461048c57806395d89b4114610494578063a457c2d71461049c578063a9059cbb146104c8576101fb565b80637b1039991461043b57806387f8ab261461045f5780638da5cb5b1461047c5780638f32d59b14610484576101fb565b8063395093511161019257806354255be01161016157806354255be0146103d757806370a0823114610405578063715018a61461042b57806376348f7114610433576101fb565b8063395093511461035a5780633a70a5ca1461038657806340c10f191461038e57806342966c68146103ba576101fb565b806318160ddd116101ce57806318160ddd146102e457806323b872dd146102fe578063265126bd14610334578063313ce5671461033c576101fb565b80630562b9f71461020057806306fdde031461021f578063095ea7b31461029c578063158ef93e146102dc575b600080fd5b61021d6004803603602081101561021657600080fd5b503561066c565b005b61022761070d565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610261578181015183820152602001610249565b50505050905090810190601f16801561028e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102c8600480360360408110156102b257600080fd5b506001600160a01b038135169060200135610739565b604080519115158252519081900360200190f35b6102c86107fe565b6102ec610807565b60408051918252519081900360200190f35b6102c86004803603606081101561031457600080fd5b506001600160a01b03813581169160208101359091169060400135610846565b6102ec610b17565b610344610b29565b6040805160ff9092168252519081900360200190f35b6102c86004803603604081101561037057600080fd5b506001600160a01b038135169060200135610b2e565b6102ec610c2a565b6102c8600480360360408110156103a457600080fd5b506001600160a01b038135169060200135610c8e565b6102c8600480360360208110156103d057600080fd5b5035610eb6565b6103df610ec4565b604080519485526020850193909352838301919091526060830152519081900360800190f35b6102ec6004803603602081101561041b57600080fd5b50356001600160a01b0316610ed1565b61021d610ede565b6102c8610f74565b610443610f88565b604080516001600160a01b039092168252519081900360200190f35b61021d6004803603602081101561047557600080fd5b5035610f97565b610443611033565b6102c8611047565b6102ec611070565b61022761109e565b6102c8600480360360408110156104b257600080fd5b506001600160a01b0381351690602001356110bc565b6102c8600480360360408110156104de57600080fd5b506001600160a01b0381351690602001356110f1565b61021d6004803603602081101561050a57600080fd5b50356001600160a01b0316611104565b61021d6004803603602081101561053057600080fd5b50356111f0565b61021d6004803603602081101561054d57600080fd5b50356001600160a01b0316611257565b6102ec6112d4565b6104436112da565b61021d6004803603602081101561058357600080fd5b50356001600160a01b03166112e9565b6102ec600480360360408110156105a957600080fd5b506001600160a01b03813581169160200135166113e3565b6102c8600480360360608110156105d757600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561060757600080fd5b82018360208201111561061957600080fd5b8035906020019184600183028401116401000000008311171561063b57600080fd5b50909250905061140e565b61021d6004803603602081101561065c57600080fd5b50356001600160a01b0316611486565b610674610f74565b6106af5760405162461bcd60e51b81526004018080602001828103825260238152602001806119e76023913960400191505060405180910390fd5b6016602160991b0133146106f45760405162461bcd60e51b81526004018080602001828103825260228152602001806119076022913960400191505060405180910390fd5b600454610707908263ffffffff6114d616565b60045550565b60408051808201909152601181527010d95b1bc81b985d1a5d9948185cdcd95d607a1b60208201525b90565b60006001600160a01b038316610796576040805162461bcd60e51b815260206004820152601a60248201527f63616e6e6f742073657420616c6c6f77616e636520666f722030000000000000604482015290519081900360640190fd5b3360008181526003602090815260408083206001600160a01b03881680855290835292819020869055805186815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60005460ff1681565b6000610811610f74565b1561083d57600454610836906b033b2e3c9fd0803ce80000009063ffffffff61153016565b9050610736565b50600254610736565b60006001600160a01b03831661088d5760405162461bcd60e51b815260040180806020018281038252602a815260200180611a0a602a913960400191505060405180910390fd5b61089684610ed1565b8211156108d45760405162461bcd60e51b81526004018080602001828103825260298152602001806119be6029913960400191505060405180910390fd5b6001600160a01b03841660009081526003602090815260408083203384529091529020548211156109365760405162461bcd60e51b8152600401808060200182810382526036815260200180611a346036913960400191505060405180910390fd5b600060fd815a9087878760405160200180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b0316815260200182815260200193505050506040516020818303038152906040526040518082805190602001908083835b602083106109c05780518252601f1990920191602091820191016109a1565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381858888f193505050503d8060008114610a23576040519150601f19603f3d011682016040523d82523d6000602084013e610a28565b606091505b50508091505080610a77576040805162461bcd60e51b815260206004820152601460248201527310d15313c81d1c985b9cd9995c8819985a5b195960621b604482015290519081900360640190fd5b6001600160a01b0385166000908152600360209081526040808320338452909152902054610aab908463ffffffff61153016565b6001600160a01b03808716600081815260036020908152604080832033845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3506001949350505050565b6000610b2461dead610ed1565b905090565b601290565b60006001600160a01b038316610b8b576040805162461bcd60e51b815260206004820152601a60248201527f63616e6e6f742073657420616c6c6f77616e636520666f722030000000000000604482015290519081900360640190fd5b3360009081526003602090815260408083206001600160a01b038716845290915281205490610bc0828563ffffffff6114d616565b3360008181526003602090815260408083206001600160a01b038b16808552908352928190208590558051858152905194955091937f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3506001949350505050565b6000610c34610f74565b610c6f5760405162461bcd60e51b81526004018080602001828103825260238152602001806119e76023913960400191505060405180910390fd5b506005546001600160a01b0316316b033b2e3c9fd0803ce80000000390565b6000610c98611572565b3315610cde576040805162461bcd60e51b815260206004820152601060248201526f13db9b1e4815934818d85b8818d85b1b60821b604482015290519081900360640190fd5b81610ceb575060016107f8565b6001600160a01b038316610d305760405162461bcd60e51b81526004018080602001828103825260268152602001806119296026913960400191505060405180910390fd5b600254610d43908363ffffffff6114d616565b600255600060fd815a6040805160006020808301919091526001600160a01b038a168284015260608083018a905283518084039091018152608090920192839052815193949391929182918401908083835b60208310610db45780518252601f199092019160209182019101610d95565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381858888f193505050503d8060008114610e17576040519150601f19603f3d011682016040523d82523d6000602084013e610e1c565b606091505b50508091505080610e6b576040805162461bcd60e51b815260206004820152601460248201527310d15313c81d1c985b9cd9995c8819985a5b195960621b604482015290519081900360640190fd5b6040805184815290516001600160a01b038616916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35060019392505050565b60006107f861dead836115b8565b6001806003600090919293565b6001600160a01b03163190565b610ee6611047565b610f25576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b600080546040516101009091046001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a360008054610100600160a81b0319169055565b63ffffffff6018602160991b013b16151590565b6001546001600160a01b031681565b3315610fdd576040805162461bcd60e51b815260206004820152601060248201526f13db9b1e4815934818d85b8818d85b1b60821b604482015290519081900360640190fd5b610fe5610f74565b6110205760405162461bcd60e51b81526004018080602001828103825260238152602001806119e76023913960400191505060405180910390fd5b600454610707908263ffffffff61153016565b60005461010090046001600160a01b031690565b6000805461010090046001600160a01b031661106161176f565b6001600160a01b031614905090565b6000610b2461107f6000610ed1565b61109261108a610b17565b611092610807565b9063ffffffff61153016565b60408051808201909152600481526343454c4f60e01b602082015290565b3360009081526003602090815260408083206001600160a01b038616845290915281205481610bc0828563ffffffff61153016565b60006110fd8383611773565b9392505050565b61110c611047565b61114b576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b6001600160a01b0381166111a6576040805162461bcd60e51b815260206004820181905260248201527f43616e6e6f7420726567697374657220746865206e756c6c2061646472657373604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b90600090a250565b6111f8611572565b331561123e576040805162461bcd60e51b815260206004820152601060248201526f13db9b1e4815934818d85b8818d85b1b60821b604482015290519081900360640190fd5b600254611251908263ffffffff6114d616565b60025550565b60005460ff16156112af576040805162461bcd60e51b815260206004820152601c60248201527f636f6e747261637420616c726561647920696e697469616c697a656400000000604482015290519081900360640190fd5b6000805460ff191660011781556002556112c8336117c4565b6112d181611104565b50565b60045481565b6005546001600160a01b031681565b6112f1611047565b611330576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b6001600160a01b03811615158061135557506005546001600160a01b03828116911614155b611399576040805162461bcd60e51b815260206004820152601060248201526f24b73b30b634b21030b2323932b9b99760811b604482015290519081900360640190fd5b600580546001600160a01b0319166001600160a01b0383169081179091556040517f4b0e16c81bce2248d2d60ed469d2fe74152253bde95ba850f22ae056723980a690600090a250565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b60008061141b8686611773565b90507fe5d4e30fb8364e57bc4d662a07d0cf36f4c34552004c4c3624620a2c1d1c03dc848460405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a195945050505050565b61148e611047565b6114cd576040805162461bcd60e51b8152602060048201819052602482015260008051602061199e833981519152604482015290519081900360640190fd5b6112d1816117c4565b6000828201838110156110fd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60006110fd83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061186f565b61157a610f74565b156115b65760405162461bcd60e51b81526004018080602001828103825260298152602001806119756029913960400191505060405180910390fd5b565b60006115c333610ed1565b8211156116015760405162461bcd60e51b81526004018080602001828103825260298152602001806119be6029913960400191505060405180910390fd5b600060fd815a60408051336020808301919091526001600160a01b038a168284015260608083018a905283518084039091018152608090920192839052815193949391929182918401908083835b6020831061166e5780518252601f19909201916020918201910161164f565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381858888f193505050503d80600081146116d1576040519150601f19603f3d011682016040523d82523d6000602084013e6116d6565b606091505b50508091505080611725576040805162461bcd60e51b815260206004820152601460248201527310d15313c81d1c985b9cd9995c8819985a5b195960621b604482015290519081900360640190fd5b6040805184815290516001600160a01b0386169133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35060019392505050565b3390565b60006001600160a01b0383166117ba5760405162461bcd60e51b815260040180806020018281038252602a815260200180611a0a602a913960400191505060405180910390fd5b6110fd83836115b8565b6001600160a01b0381166118095760405162461bcd60e51b815260040180806020018281038252602681526020018061194f6026913960400191505060405180910390fd5b600080546040516001600160a01b038085169361010090930416917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b600081848411156118fe5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156118c35781810151838201526020016118ab565b50505050905090810190601f1680156118f05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b50505090039056fe4f6e6c79204c32546f4c314d6573736167655061737365722063616e2063616c6c2e6d696e7420617474656d7074656420746f2072657365727665642061646472657373203078304f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737354686973206d6574686f64206973206e6f206c6f6e67657220737570706f7274656420696e204c322e4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65727472616e736665722076616c75652065786365656465642062616c616e6365206f662073656e64657254686973206d6574686f64206973206e6f7420737570706f7274656420696e204c312e7472616e7366657220617474656d7074656420746f2072657365727665642061646472657373203078307472616e736665722076616c75652065786365656465642073656e646572277320616c6c6f77616e636520666f72207370656e646572a265627a7a72315820883737fc20fdb370fa6d2083191051192e78e14b9c9ed5974e58cd42d97124f264736f6c63430005110032"
+ }
+ },
+ "config": {
+ "chainId": 1337,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "muirGlacierBlock": 0,
+ "berlinBlock": 0,
+ "londonBlock": 0,
+ "arrowGlacierBlock": 0,
+ "grayGlacierBlock": 0,
+ "shanghaiTime": 0,
+ "cel2Time": 0,
+ "terminalTotalDifficulty": 0,
+ "terminalTotalDifficultyPassed": true
+ }
+ },
+ "context": {
+ "baseFeePerGas": "875000000",
+ "number": "2",
+ "difficulty": "0",
+ "timestamp": "1721214739",
+ "gasLimit": "11522469",
+ "miner": "0x0000000000000000000000000000000000000000"
+ },
+ "input": "0x02f8ad820539018084684ee18082857e94471ece3750da237f93b8e339c536989b8978a43880b844a9059cbb000000000000000000000000000000000000000000000000000000000000dead0000000000000000000000000000000000000000000000000000000000000064c080a08e63474fbf5acffb6848dca77cb51ab6341ee34e2a8dd3e13af9bb99363eb068a0439aee6906e998ba44680f29c309bd82621f7eae4591e9439ec2636b0ad933c6",
+ "result": {
+ "from": "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a",
+ "gas": "0x857e",
+ "gasUsed": "0x83c9",
+ "to": "0x471ece3750da237f93b8e339c536989b8978a438",
+ "input": "0xa9059cbb000000000000000000000000000000000000000000000000000000000000dead0000000000000000000000000000000000000000000000000000000000000064",
+ "output": "0x0000000000000000000000000000000000000000000000000000000000000001",
+ "calls": [
+ {
+ "from": "0x471ece3750da237f93b8e339c536989b8978a438",
+ "gas": "0x2c16",
+ "gasUsed": "0x2328",
+ "to": "0x00000000000000000000000000000000000000fd",
+ "input": "0x00000000000000000000000042cf1bbc38baaa3c4898ce8790e21ed2738c6a4a000000000000000000000000000000000000000000000000000000000000dead0000000000000000000000000000000000000000000000000000000000000064",
+ "value": "0x0",
+ "type": "CALL"
+ }
+ ],
+ "value": "0x0",
+ "type": "CALL"
+ }
+}
diff --git op-geth/eth/tracers/internal/tracetest/testdata/prestate_tracer/fee_currency.json Celo/eth/tracers/internal/tracetest/testdata/prestate_tracer/fee_currency.json
new file mode 100644
index 0000000000000000000000000000000000000000..d31da99ff68c4bee41fb2a855d287b36af95ddd4
--- /dev/null
+++ Celo/eth/tracers/internal/tracetest/testdata/prestate_tracer/fee_currency.json
@@ -0,0 +1,98 @@
+{
+ "genesis": {
+ "difficulty": "0",
+ "extraData": "0xd8820100846765746888676f312e32312e368664617277696e",
+ "gasLimit": "11533720",
+ "hash": "0x64579a9548b63477d50175fe20108afda5ffcbc38d7ff709dd2f07cae0077bdb",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "mixHash": "0xa5bdfbdd8f30e8eb34108e78f94274cd60dd348aed7428a47330f94e6d8f0378",
+ "nonce": "0x0000000000000000",
+ "number": "3",
+ "stateRoot": "0xe7ce9d40973a218b36332fcfd42b102e1b1f620a914f344de0f996b429e26b2c",
+ "timestamp": "1721119176",
+ "totalDifficulty": "1",
+ "withdrawals": [],
+ "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "alloc": {
+ "0x0000000000000000000000000000000000000000": {
+ "balance": "0xa410",
+ "nonce": "1"
+ },
+ "0x000000000000000000000000000000000000ce16": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806358cf96721161008c57806395d89b411161006657806395d89b41146101ca578063a457c2d7146101d2578063a9059cbb146101e5578063dd62ed3e146101f857600080fd5b806358cf96721461016c5780636a30b2531461018157806370a082311461019457600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015957600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f99190610c15565b60405180910390f35b610115610110366004610cb1565b6102d0565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610cdb565b6102e8565b604051601281526020016100f9565b610115610167366004610cb1565b61030e565b61017f61017a366004610cb1565b61035a565b005b61017f61018f366004610d17565b61041e565b6101296101a2366004610d8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6100ec610510565b6101156101e0366004610cb1565b61051f565b6101156101f3366004610cb1565b6105fb565b610129610206366004610daa565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461024d90610ddd565b80601f016020809104026020016040519081016040528092919081815260200182805461027990610ddd565b80156102c65780601f1061029b576101008083540402835291602001916102c6565b820191906000526020600020905b8154815290600101906020018083116102a957829003601f168201915b5050505050905090565b6000336102de818585610609565b5060019392505050565b6000336102f68582856107bc565b610301858585610893565b60019150505b9392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906102de9082908690610355908790610e5f565b610609565b33156103c7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906103fc908490610e77565b9250508190555080600260008282546104159190610e77565b90915550505050565b3315610486576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064016103be565b73ffffffffffffffffffffffffffffffffffffffff8816600090815260208190526040812080548692906104bb908490610e5f565b909155506104cc9050888683610b46565b6104d69085610e5f565b93506104e3888885610b46565b6104ed9085610e5f565b935083600260008282546105019190610e5f565b90915550505050505050505050565b60606004805461024d90610ddd565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909190838110156105e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016103be565b6105f08286868403610609565b506001949350505050565b6000336102de818585610893565b73ffffffffffffffffffffffffffffffffffffffff83166106ab576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff821661074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461088d5781811015610880576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103be565b61088d8484848403610609565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610936576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff82166109d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220858503905591851681529081208054849290610ad3908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b3991815260200190565b60405180910390a361088d565b600073ffffffffffffffffffffffffffffffffffffffff8316610b6b57506000610307565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208054849290610ba0908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c0691815260200190565b60405180910390a35092915050565b600060208083528351808285015260005b81811015610c4257858101830151858201604001528201610c26565b81811115610c54576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c88565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c88565b9250610d0760208501610c88565b9150604084013590509250925092565b600080600080600080600080610100898b031215610d3457600080fd5b610d3d89610c88565b9750610d4b60208a01610c88565b9650610d5960408a01610c88565b9550610d6760608a01610c88565b979a969950949760808101359660a0820135965060c0820135955060e0909101359350915050565b600060208284031215610da157600080fd5b61030782610c88565b60008060408385031215610dbd57600080fd5b610dc683610c88565b9150610dd460208401610c88565b90509250929050565b600181811c90821680610df157607f821691505b602082108103610e2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610e7257610e72610e30565b500190565b600082821015610e8957610e89610e30565b50039056fea164736f6c634300080f000a",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000",
+ "0x544d4f2940ad90cc7147a86952e118002f47e179e05bd1add1e9972168d958aa": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x85de51d5625304e72107ba46e4bf731a09f455f609080a043a4ca2c0db099937": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
+ }
+ },
+ "0x00000000000000000000000000000000deadbeef": {
+ "balance": "0x2"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc787f65890fb62",
+ "nonce": "3"
+ }
+ },
+ "config": {
+ "chainId": 1337,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "muirGlacierBlock": 0,
+ "berlinBlock": 0,
+ "londonBlock": 0,
+ "arrowGlacierBlock": 0,
+ "grayGlacierBlock": 0,
+ "shanghaiTime": 0,
+ "cel2Time": 0,
+ "terminalTotalDifficulty": 0,
+ "terminalTotalDifficultyPassed": true
+ }
+ },
+ "context": {
+ "baseFeePerGas": "670833297",
+ "number": "4",
+ "difficulty": "0",
+ "timestamp": "1721119177",
+ "gasLimit": "11544982",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "feeCurrencyContext": {
+ "exchangeRates": {
+ "0x000000000000000000000000000000000000cE16": [2, 1]
+ },
+ "intrinsicGasCosts": {
+ "0x000000000000000000000000000000000000cE16": 50000
+ }
+ }
+ },
+ "input": "0x7bf87e8205390380847735940083015f909400000000000000000000000000000000deadbeef0280c094000000000000000000000000000000000000ce1680a06b175ce301873e64da4ef5c2fa5c8f6eb61b9ab1d7bfbe103812a32ad94a5c4fa05d55f5f23e76ce11491f4776299a1a65d4af48924b330a7681e394b2ef8e173b",
+ "result": {
+ "0x0000000000000000000000000000000000000000": {
+ "balance": "0xa410",
+ "nonce": 1
+ },
+ "0x000000000000000000000000000000000000ce16": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806358cf96721161008c57806395d89b411161006657806395d89b41146101ca578063a457c2d7146101d2578063a9059cbb146101e5578063dd62ed3e146101f857600080fd5b806358cf96721461016c5780636a30b2531461018157806370a082311461019457600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015957600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f99190610c15565b60405180910390f35b610115610110366004610cb1565b6102d0565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610cdb565b6102e8565b604051601281526020016100f9565b610115610167366004610cb1565b61030e565b61017f61017a366004610cb1565b61035a565b005b61017f61018f366004610d17565b61041e565b6101296101a2366004610d8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6100ec610510565b6101156101e0366004610cb1565b61051f565b6101156101f3366004610cb1565b6105fb565b610129610206366004610daa565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461024d90610ddd565b80601f016020809104026020016040519081016040528092919081815260200182805461027990610ddd565b80156102c65780601f1061029b576101008083540402835291602001916102c6565b820191906000526020600020905b8154815290600101906020018083116102a957829003601f168201915b5050505050905090565b6000336102de818585610609565b5060019392505050565b6000336102f68582856107bc565b610301858585610893565b60019150505b9392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906102de9082908690610355908790610e5f565b610609565b33156103c7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906103fc908490610e77565b9250508190555080600260008282546104159190610e77565b90915550505050565b3315610486576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064016103be565b73ffffffffffffffffffffffffffffffffffffffff8816600090815260208190526040812080548692906104bb908490610e5f565b909155506104cc9050888683610b46565b6104d69085610e5f565b93506104e3888885610b46565b6104ed9085610e5f565b935083600260008282546105019190610e5f565b90915550505050505050505050565b60606004805461024d90610ddd565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909190838110156105e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016103be565b6105f08286868403610609565b506001949350505050565b6000336102de818585610893565b73ffffffffffffffffffffffffffffffffffffffff83166106ab576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff821661074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461088d5781811015610880576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103be565b61088d8484848403610609565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610936576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff82166109d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220858503905591851681529081208054849290610ad3908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b3991815260200190565b60405180910390a361088d565b600073ffffffffffffffffffffffffffffffffffffffff8316610b6b57506000610307565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208054849290610ba0908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c0691815260200190565b60405180910390a35092915050565b600060208083528351808285015260005b81811015610c4257858101830151858201604001528201610c26565b81811115610c54576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c88565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c88565b9250610d0760208501610c88565b9150604084013590509250925092565b600080600080600080600080610100898b031215610d3457600080fd5b610d3d89610c88565b9750610d4b60208a01610c88565b9650610d5960408a01610c88565b9550610d6760608a01610c88565b979a969950949760808101359660a0820135965060c0820135955060e0909101359350915050565b600060208284031215610da157600080fd5b61030782610c88565b60008060408385031215610dbd57600080fd5b610dc683610c88565b9150610dd460208401610c88565b90509250929050565b600181811c90821680610df157607f821691505b602082108103610e2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610e7257610e72610e30565b500190565b600082821015610e8957610e89610e30565b50039056fea164736f6c634300080f000a",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000",
+ "0x544d4f2940ad90cc7147a86952e118002f47e179e05bd1add1e9972168d958aa": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x85de51d5625304e72107ba46e4bf731a09f455f609080a043a4ca2c0db099937": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
+ }
+ },
+ "0x00000000000000000000000000000000deadbeef": {
+ "balance": "0x2"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc787f65890fb62",
+ "nonce": 3
+ }
+ }
+}
(new)
+115
-0
diff --git op-geth/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/fee_currency.json Celo/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/fee_currency.json
new file mode 100644
index 0000000000000000000000000000000000000000..b3b405eb5fb1060b971fea7214d2dfe5c349a462
--- /dev/null
+++ Celo/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/fee_currency.json
@@ -0,0 +1,115 @@
+{
+ "genesis": {
+ "baseFeePerGas": "1000000000",
+ "blobGasUsed": "0",
+ "difficulty": "0",
+ "excessBlobGas": "0",
+ "extraData": "0x",
+ "gasLimit": "11500000",
+ "hash": "0xc7251007be38d4facc71896878b1540ab6fb3b789aa5993f8cc4b7be7190f42e",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "nonce": "0x0000000000000000",
+ "number": "0",
+ "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "stateRoot": "0x951ef51a39ef66705f08bcaf5ead3fec1de67effda449899b83d9e1800a1463d",
+ "timestamp": "0",
+ "totalDifficulty": "0",
+ "withdrawals": [],
+ "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "alloc": {
+ "0x0000000000000000000000000000000000000000": {
+ "balance": "0x0"
+ },
+ "0x000000000000000000000000000000000000ce16": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806358cf96721161008c57806395d89b411161006657806395d89b41146101ca578063a457c2d7146101d2578063a9059cbb146101e5578063dd62ed3e146101f857600080fd5b806358cf96721461016c5780636a30b2531461018157806370a082311461019457600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015957600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f99190610c15565b60405180910390f35b610115610110366004610cb1565b6102d0565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610cdb565b6102e8565b604051601281526020016100f9565b610115610167366004610cb1565b61030e565b61017f61017a366004610cb1565b61035a565b005b61017f61018f366004610d17565b61041e565b6101296101a2366004610d8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6100ec610510565b6101156101e0366004610cb1565b61051f565b6101156101f3366004610cb1565b6105fb565b610129610206366004610daa565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461024d90610ddd565b80601f016020809104026020016040519081016040528092919081815260200182805461027990610ddd565b80156102c65780601f1061029b576101008083540402835291602001916102c6565b820191906000526020600020905b8154815290600101906020018083116102a957829003601f168201915b5050505050905090565b6000336102de818585610609565b5060019392505050565b6000336102f68582856107bc565b610301858585610893565b60019150505b9392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906102de9082908690610355908790610e5f565b610609565b33156103c7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906103fc908490610e77565b9250508190555080600260008282546104159190610e77565b90915550505050565b3315610486576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064016103be565b73ffffffffffffffffffffffffffffffffffffffff8816600090815260208190526040812080548692906104bb908490610e5f565b909155506104cc9050888683610b46565b6104d69085610e5f565b93506104e3888885610b46565b6104ed9085610e5f565b935083600260008282546105019190610e5f565b90915550505050505050505050565b60606004805461024d90610ddd565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909190838110156105e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016103be565b6105f08286868403610609565b506001949350505050565b6000336102de818585610893565b73ffffffffffffffffffffffffffffffffffffffff83166106ab576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff821661074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461088d5781811015610880576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103be565b61088d8484848403610609565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610936576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff82166109d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220858503905591851681529081208054849290610ad3908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b3991815260200190565b60405180910390a361088d565b600073ffffffffffffffffffffffffffffffffffffffff8316610b6b57506000610307565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208054849290610ba0908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c0691815260200190565b60405180910390a35092915050565b600060208083528351808285015260005b81811015610c4257858101830151858201604001528201610c26565b81811115610c54576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c88565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c88565b9250610d0760208501610c88565b9150604084013590509250925092565b600080600080600080600080610100898b031215610d3457600080fd5b610d3d89610c88565b9750610d4b60208a01610c88565b9650610d5960408a01610c88565b9550610d6760608a01610c88565b979a969950949760808101359660a0820135965060c0820135955060e0909101359350915050565b600060208284031215610da157600080fd5b61030782610c88565b60008060408385031215610dbd57600080fd5b610dc683610c88565b9150610dd460208401610c88565b90509250929050565b600181811c90821680610df157607f821691505b602082108103610e2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610e7257610e72610e30565b500190565b600082821015610e8957610e89610e30565b50039056fea164736f6c634300080f000a",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000",
+ "0x544d4f2940ad90cc7147a86952e118002f47e179e05bd1add1e9972168d958aa": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x85de51d5625304e72107ba46e4bf731a09f455f609080a043a4ca2c0db099937": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
+ }
+ },
+ "0x00000000000000000000000000000000deadbeef": {
+ "balance": "0x0"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc75e2d63100000"
+ }
+ },
+ "config": {
+ "chainId": 1337,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "muirGlacierBlock": 0,
+ "berlinBlock": 0,
+ "londonBlock": 0,
+ "arrowGlacierBlock": 0,
+ "grayGlacierBlock": 0,
+ "shanghaiTime": 0,
+ "cancunTime": 0,
+ "cel2Time": 0,
+ "gingerbreadBlock": 0,
+ "terminalTotalDifficulty": 0,
+ "terminalTotalDifficultyPassed": true
+ }
+ },
+ "context": {
+ "number": "1",
+ "difficulty": "0",
+ "timestamp": "1724850836",
+ "gasLimit": "11511229",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "baseFeePerGas": "875000000",
+ "feeCurrencyContext": {
+ "exchangeRates": {
+ "0x000000000000000000000000000000000000cE16": [2, 1]
+ },
+ "intrinsicGasCosts": {
+ "0x000000000000000000000000000000000000cE16": 50000
+ }
+ }
+ },
+ "input": "0x7bf87e8205398080847735940083015f909400000000000000000000000000000000deadbeef0280c094000000000000000000000000000000000000ce1680a01d720daf50795cffe30b998633b4a45f2fd3b4deed459d4cb47386090ecaadcba0098844fc954eccaea1463a2322072acc58782dc31e95cc640d5fe141650cb039",
+ "result": {
+ "post": {
+ "0x000000000000000000000000000000000000ce16": {
+ "storage": {
+ "0x544d4f2940ad90cc7147a86952e118002f47e179e05bd1add1e9972168d958aa": "0x00000000000000000000000000000000000000000000000000007101351d0400",
+ "0x85de51d5625304e72107ba46e4bf731a09f455f609080a043a4ca2c0db099937": "0x0000000000000000000000000000000000000000000000056bc6ed2c2df2fc00"
+ }
+ },
+ "0x00000000000000000000000000000000deadbeef": {
+ "balance": "0x2"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc75e2d630ffffe",
+ "nonce": 1
+ }
+ },
+ "pre": {
+ "0x000000000000000000000000000000000000ce16": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806358cf96721161008c57806395d89b411161006657806395d89b41146101ca578063a457c2d7146101d2578063a9059cbb146101e5578063dd62ed3e146101f857600080fd5b806358cf96721461016c5780636a30b2531461018157806370a082311461019457600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015957600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f99190610c15565b60405180910390f35b610115610110366004610cb1565b6102d0565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610cdb565b6102e8565b604051601281526020016100f9565b610115610167366004610cb1565b61030e565b61017f61017a366004610cb1565b61035a565b005b61017f61018f366004610d17565b61041e565b6101296101a2366004610d8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6100ec610510565b6101156101e0366004610cb1565b61051f565b6101156101f3366004610cb1565b6105fb565b610129610206366004610daa565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461024d90610ddd565b80601f016020809104026020016040519081016040528092919081815260200182805461027990610ddd565b80156102c65780601f1061029b576101008083540402835291602001916102c6565b820191906000526020600020905b8154815290600101906020018083116102a957829003601f168201915b5050505050905090565b6000336102de818585610609565b5060019392505050565b6000336102f68582856107bc565b610301858585610893565b60019150505b9392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906102de9082908690610355908790610e5f565b610609565b33156103c7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906103fc908490610e77565b9250508190555080600260008282546104159190610e77565b90915550505050565b3315610486576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4f6e6c7920564d2063616e2063616c6c0000000000000000000000000000000060448201526064016103be565b73ffffffffffffffffffffffffffffffffffffffff8816600090815260208190526040812080548692906104bb908490610e5f565b909155506104cc9050888683610b46565b6104d69085610e5f565b93506104e3888885610b46565b6104ed9085610e5f565b935083600260008282546105019190610e5f565b90915550505050505050505050565b60606004805461024d90610ddd565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909190838110156105e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016103be565b6105f08286868403610609565b506001949350505050565b6000336102de818585610893565b73ffffffffffffffffffffffffffffffffffffffff83166106ab576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff821661074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461088d5781811015610880576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103be565b61088d8484848403610609565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610936576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff82166109d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016103be565b73ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220858503905591851681529081208054849290610ad3908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b3991815260200190565b60405180910390a361088d565b600073ffffffffffffffffffffffffffffffffffffffff8316610b6b57506000610307565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208054849290610ba0908490610e5f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c0691815260200190565b60405180910390a35092915050565b600060208083528351808285015260005b81811015610c4257858101830151858201604001528201610c26565b81811115610c54576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c88565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c88565b9250610d0760208501610c88565b9150604084013590509250925092565b600080600080600080600080610100898b031215610d3457600080fd5b610d3d89610c88565b9750610d4b60208a01610c88565b9650610d5960408a01610c88565b9550610d6760608a01610c88565b979a969950949760808101359660a0820135965060c0820135955060e0909101359350915050565b600060208284031215610da157600080fd5b61030782610c88565b60008060408385031215610dbd57600080fd5b610dc683610c88565b9150610dd460208401610c88565b90509250929050565b600181811c90821680610df157607f821691505b602082108103610e2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610e7257610e72610e30565b500190565b600082821015610e8957610e89610e30565b50039056fea164736f6c634300080f000a",
+ "storage": {
+ "0x85de51d5625304e72107ba46e4bf731a09f455f609080a043a4ca2c0db099937": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
+ }
+ },
+ "0x00000000000000000000000000000000deadbeef": {
+ "balance": "0x0"
+ },
+ "0x42cf1bbc38baaa3c4898ce8790e21ed2738c6a4a": {
+ "balance": "0x56bc75e2d63100000"
+ }
+ }
+ },
+ "tracerConfig": {
+ "diffMode": true
+ }
+}
diff --git op-geth/fork.yaml Celo/fork.yaml
index cf31c24316456e1c37492928b6db537d253b9e41..52a2cd914303df38433b898379cb246a41fb631d 100644
--- op-geth/fork.yaml
+++ Celo/fork.yaml
@@ -1,345 +1,70 @@
-title: "op-geth - go-ethereum fork diff overview"
+# yamllint disable rule:line-length rule:document-start
+title: "Celo <> OP op-geth forkdiff"
footer: |
- Fork-diff overview of [`op-geth`](https://github.com/ethereum-optimism/op-geth), a fork of [`go-ethereum`](https://github.com/ethereum/go-ethereum).
- and execution-engine of the [OP-stack](https://github.com/ethereum-optimism/optimism).
+ Fork-diff overview of changes made in [Celo's `op-geth`](https://github.com/celo-org/op-geth),
+ a fork of [Optimism's `op-geth`](https://github.com/ethereum-optimism/op-geth).
base:
- name: go-ethereum
- url: https://github.com/ethereum/go-ethereum
- hash: f3c696fa1db75d0f78ea47dd0975f6f0de6fdd84 # v1.14.11
-fork:
name: op-geth
- url: https://github.com/ethereum-optimism/op-geth
- ref: refs/heads/optimism
+ url: https://github.com/celo-org/op-geth
+ ref: refs/remotes/origin/celo11-upstream
+fork:
+ name: Celo
+ url: https://github.com/celo-org/op-geth
+ ref: HEAD
def:
- title: "op-geth"
+ title: "Celo's op-geth"
description: |
- This is an overview of the changes in [`op-geth`](https://github.com/ethereum-optimism/op-geth),
- a fork of [`go-ethereum`](https://github.com/ethereum/go-ethereum), part of the OP-stack.
-
- The OP-stack architecture is modular, following the Consensus/Execution split of post-Merge Ethereum L1:
-
- - [`op-node`](https://github.com/ethereum-optimism/optimism/tree/develop/op-node) implements most rollup-specific functionality as Consensus-Layer, similar to a L1 beacon-node.
- - [`op-geth`](https://github.com/ethereum-optimism/op-geth) implements the Execution-Layer, with **minimal changes** for a secure Ethereum-equivalent application environment.
+ This is an overview of the changes in [Celo's `op-geth` implementation](https://github.com/celo-org/op-geth),
+ a fork of [Optimism's `op-geth`](https://github.com/ethereum-optimism/op-geth).
- Related [op-stack specifications](https://github.com/ethereum-optimism/optimism/tree/develop/specs):
+ For differences between the base `op-geth` and `go-ethereum`, check out Optimism's
+ [fork-diff overview of changes](https://op-geth.optimism.io/).
- - [L2 Execution Engine spec](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md)
- - [Deposit Transaction spec](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md)
sub:
- - title: "Core modifications"
+ - title: "Celo-specific features"
sub:
- - title: "State-transition modifications"
- description: ""
- sub:
- - title: "Deposit Transaction type"
- description: |
- The Bedrock upgrade introduces a `Deposit` transaction-type (`0x7E`) to enable both users and the
- rollup system itself to change the L2 state based on L1 events and system rules as
- [specified](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md).
- globs:
- - "core/types/deposit_tx.go"
- - "core/types/transaction_marshalling.go"
- - "core/types/transaction_signing.go"
- - title: "Transaction properties"
- description: |
- The `Transaction` type now exposes the deposit-transaction and L1-cost properties required for the rollup.
- globs:
- - "core/types/transaction.go"
- - "core/types/tx_access_list.go"
- - "core/types/tx_dynamic_fee.go"
- - "core/types/tx_legacy.go"
- - "core/types/tx_blob.go"
- - title: "EVM enhancements"
- description: |
- Apply L1 cost computation, and add EVM configuration for tooling and more:
- - Disable bytecode size-limits (for large test/script contracts).
- - Prank (solidity test terminology) the EVM-call message-sender.
- - Override precompiles, to insert tooling precompiles and optimize precompile proving.
- globs:
- - "core/vm/evm.go"
- - "core/vm/interpreter.go"
- - "core/vm/gas_table.go"
- - title: "L1 cost computation"
- description: |
- Transactions must pay an additional L1 cost based on the amount of rollup-data-gas they consume,
- estimated based on gas-price-oracle information and encoded tx size."
- globs:
- - "core/evm.go"
- - "core/types/rollup_cost.go"
- - "core/state_processor.go"
- - "core/state_prefetcher.go"
- - title: Transaction processing
- description: |
- Deposit transactions have special processing rules: gas is pre-paid on L1,
- and deposits with EVM-failure are included with rolled back changes (except mint).
- For regular transactions, at the end of the transition, the 1559 burn and L1 cost are routed to vaults.
- globs:
- - "core/state_transition.go"
- - title: "Core Error definitions"
- globs:
- - "core/error.go"
- - title: "Gaslimit and EIP-1559 Params"
- description: |
- The gaslimit is free to be set by the Engine API caller, instead of enforcing adjustments of the
- gaslimit in increments of 1/1024 of the previous gaslimit. The elasticity-multiplier and
- base-fee-max-change-denominator EIP-1559 parameters can also be set by the Engine API caller through the
- ExtraData field. The gaslimit and EIP-1559 parameters are changed (and limited) through the
- `SystemConfig` contract.
- globs:
- - "consensus/misc/eip1559/*"
- - title: "Consensus tweaks"
- description: |
- The Engine API is activated at the Merge transition, with a Total Terminal Difficulty (TTD).
- The rollup starts post-merge, and thus sets the TTD to 0.
- The TTD is always "reached" starting at the bedrock block.
- globs:
- - "consensus/beacon/consensus.go"
- - title: "Legacy OP-mainnet / OP-goerli header-verification support"
- description: |
- Pre-Bedrock OP-mainnet and OP-Goerli had differently formatted block-headers, loosely compatible with the geth types (since it was based on Clique).
- However, due to differences like the extra-data length (97+ bytes), these legacy block-headers need special verification.
- The pre-merge "consensus" fallback is set to this custom but basic verifier, to accept these headers when syncing a pre-bedrock part of the chain,
- independent of any clique code or configuration (which may be removed from geth at a later point).
- All the custom verifier has to do is accept the headers, as the headers are already verified by block-hash through the reverse-header-sync.
- globs:
- - "consensus/beacon/oplegacy.go"
- - title: "Engine API modifications"
- description: |
- The Engine API is extended to insert transactions into the block and optionally exclude the tx-pool, to
- reproduce the exact block of the sequencer from just the inputs, as derived from L1 by the rollup-node. See
- [L2 execution engine specs](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md).
- It is also extended to support dynamic EIP-1559 parameters. See
- [Holocene execution engine specs](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md).
+ - title: "Celo token duality"
+ description: "The Celo token is both the native token and ERC-20 compatible."
globs:
- - "beacon/engine/types.go"
- - "beacon/engine/gen_blockparams.go"
- - "eth/catalyst/api.go"
- - title: "Block-building modifications"
- description: |
- The block-building code (in the "miner" package because of Proof-Of-Work legacy of ethereum) implements the
- changes to support the transaction-inclusion, tx-pool toggle, gaslimit, and EIP-1559 parameters of the
- Engine API.
- This also includes experimental support for interop executing-messages to be verified through an RPC.
+ - "core/vm/celo_contracts*.go"
+ - "core/vm/contracts*.go"
+ - "core/vm/evm.go"
+ - title: "Celo fee currencies"
+ description: "Fee currencies allow to pay transaction fees with ERC20 tokens."
globs:
- - "miner/*"
- - title: "Tx-pool tx cost updates"
+ - "core/evm.go"
+ - "contracts/fee_currencies.go"
+ - "core/blockchain_celo_test.go"
+ - "core/celo_evm.go"
+ - "core/state_processor.go"
+ - "core/state_transition.go"
+ - "core/types/celo_dynamic_fee_tx.go"
+ - "core/types/celo_transaction_signing.go"
+ - "core/txpool/*"
+ - "core/txpool/*/*"
+ - "core/types/receipt*.go"
+ - "core/types/transaction*.go"
+ - "core/types/tx*.go"
+ - title: "Celo contracts"
description: |
- Transaction queueing and inclusion needs to account for the L1 cost component.
+ Contract bindings are necessary for token duality and gas currencies.
+ The corresponding contracts are included to generate the bindings and test these features.
globs:
- - "core/txpool/**/*"
- - "core/txpool/legacypool/*"
- - title: "RIP-7212"
- description: ""
- globs:
- - "core/vm/contracts.go"
- - "crypto/secp256r1/publickey.go"
- - "crypto/secp256r1/verifier.go"
- - title: "Chain Configuration"
- sub:
+ - "contracts/celo/*"
+ - "contracts/celo/*/*"
+ - "contracts/config/registry_ids.go"
+ - "core/celo_backend.go"
+ - "core/celo_genesis.go"
- title: "Chain config"
- description: |
- The rollup functionality is enabled with the `optimism` field in the chain config.
- The EIP-1559 parameters are configurable to adjust for faster more frequent and smaller blocks.
- The parameters can be overriden for testing.
+ description: ""
globs:
- "params/config.go"
- "params/protocol_params.go"
- "core/genesis.go"
- - title: "Chain config cleanup"
- description: |
- The optimism Goerli testnet used clique-config data to make geth internals accept blocks.
- Post-bedrock the beacon-consensus (i.e. follow Engine API) is now used, and the clique config is removed.
+ - title: "E2E tests"
globs:
- - "core/rawdb/accessors_metadata.go"
- - title: Genesis loading
- globs:
- - "core/gen_genesis.go"
- - title: "Superchain config"
- description: Testing of the superchain configuration
- globs:
- - "core/superchain.go"
- - "params/superchain.go"
- - title: "Node modifications"
- description: Changes to the node configuration and services.
- sub:
- - title: "CLI"
- sub:
- - title: "Flags"
- description: |
- Flag changes:
- - Transactions can be forwarded to an RPC for sequencing.
- - Historical calls can be forwarded to a legacy node.
- - The tx pool propagation can be enabled/disabled.
- - The Optimism bedrock fork activation can be changed for testing.
- globs:
- - "cmd/utils/flags.go"
- - "cmd/geth/main.go"
- - "internal/flags/categories.go"
- - "cmd/geth/config.go"
- - title: "Versioning"
- description: List the op-geth and upstream go-ethereum versions.
- globs:
- - "cmd/geth/misccmd.go"
- - "params/version.go"
- - "build/ci.go"
- - title: Node config
- globs:
- - "eth/ethconfig/config.go"
- - title: Tx gossip disable option
- globs:
- - "eth/handler.go"
- - "eth/handler_eth.go"
- - title: Warn on missing hardfork data
- globs:
- - "core/blockchain.go"
- - title: Optional Engine API extensions
- globs:
- - "eth/catalyst/superchain.go"
- - title: Support legacy DBs when snap-syncing
- description: Snap-sync does not serve unprefixed code by default.
- globs:
- - "core/blockchain_reader.go"
- - "eth/protocols/snap/handler.go"
- - title: Historical data for Snap-sync
- description: Snap-sync has access to trusted Deposit Transaction Nonce Data.
- globs:
- - "eth/downloader/downloader.go"
- - "eth/downloader/receiptreference.go"
- - title: Discv5 node discovery
- description: Fix discv5 option to allow discv5 to be an active source for node-discovery.
- globs:
- - "p2p/server.go"
- - title: Bootnodes
- description: Discovery bootnode addresses.
- globs:
- - "params/bootnodes.go"
- - title: Generated TOML config update
- globs:
- - "eth/ethconfig/gen_config.go"
- - title: "TrieDB PathDB-journal migration"
- description: |
- From version 1.13.15 to 1.14.0 upstream go-ethereum changed the PathDB journal version, without migration path.
- For a better experience when updating op-geth, to not roll back any journaled stage changes,
- op-geth implements backward-compatibility with the old journal version.
- See upstream Geth PR 28940, and op-geth PR 368 for details.
- globs:
- - "triedb/pathdb/journal.go"
- - title: "Single threaded execution"
- description: |
- The cannon fault proofs virtual machine does not support the creation of threads. To ensure compatibility,
- thread creation is avoided when only a single CPU is available.
- globs:
- - "core/state/workers.go"
- - "trie/hasher.go"
- - title: "Interop message checking"
- description: |
- The interop upgrade introduces cross-chain message.
- Transactions are checked for cross-chain message safety before and during inclusion into a block.
- This also includes tx-pool ingress filtering.
- globs:
- - "eth/interop.go"
- - "core/txpool/ingress_filters.go"
- - title: "User API enhancements"
- description: "Encode the Deposit Tx properties, the L1 costs, and daisy-chain RPC-calls for pre-Bedrock historical data"
- sub:
- - title: "Receipts metadata"
- description: |
- Pre-Bedrock L1-cost receipt data is loaded from the database if available, and post-Bedrock the L1-cost
- metadata is hydrated on-the-fly based on the L1 fee information in the corresponding block.
- globs:
- - "core/types/receipt.go"
- - "core/types/gen_receipt_json.go"
- - "core/rawdb/accessors_chain.go"
- - title: "API Backend"
- description: |
- Forward transactions to the sequencer if configured.
- globs:
- - "eth/api_backend.go"
- - "eth/backend.go"
- - "internal/ethapi/backend.go"
- - title: "Apply L1 cost in API responses"
- globs:
- - "eth/state_accessor.go"
- - title: API frontend
- description: Format deposit and L1-cost data in transaction responses. Add `debug_chainConfig` API.
- globs:
- - "internal/ethapi/api.go"
- - "rpc/errors.go"
- - title: Tracer RPC daisy-chain
- description: Forward pre-bedrock tracing calls to legacy node.
- globs:
- - "eth/tracers/api.go"
- - title: "Daisy Chain tests"
- ignore:
- - "internal/ethapi/transaction_args_test.go"
- - "ethclient/ethclient_test.go"
- - "eth/tracers/api_test.go"
- - title: Debug API
- description: Fix Debug API block marshaling to include deposits
- globs:
- - "eth/api_debug.go"
- - title: Eth gasprice suggestions
- description: gasprice suggestion adjustments to accommodate faster L2 blocks and lower fees.
- globs:
- - "eth/gasprice/gasprice.go"
- - "eth/gasprice/optimism-gasprice.go"
- - title: API testvector fix
- description: |
- Upstream test of broken behavior; in Optimism, a zero signature is valid (pre-bedrock for deposit-txs),
- and the chain ID formula on signature data must not be used, or an underflow happens.
- globs:
- - "internal/ethapi/testdata/eth_getBlockByNumber-tag-pending-fullTx.json"
- - title: "4337 Improvements"
- description: ""
- sub:
- - title: eth_sendRawTransactionConditional
- description: sequencer api for conditional transaction inclusion enforced out of protocol
- globs:
- - "cmd/geth/main.go"
- - "cmd/utils/flags.go"
- - "core/state/statedb.go"
- - "core/state/statedb_test.go"
- - "core/types/block.go"
- - "core/types/block_test.go"
- - "core/types/transaction.go"
- - "core/types/transaction_conditional.go"
- - "core/types/transaction_conditional.go"
- - "core/types/transaction_conditional_test.go"
- - "core/types/gen_transaction_conditional_json.go"
- - "eth/backend.go"
- - "eth/ethconfig/config.go"
- - "eth/protocols/eth/broadcast.go"
- - "internal/sequencerapi/api.go"
- - "miner/miner.go"
- - "miner/miner_test.go"
- - "miner/worker.go"
- - "params/conditional_tx_params.go"
- - "rpc/json.go"
- - title: "Geth extras"
- description: Extend the tools available in geth to improve external testing and tooling.
- sub:
- - title: Simulated Backend
- globs:
- - "accounts/abi/bind/backends/simulated.go"
- - "ethclient/simulated/backend.go"
- - title: Live tracer update
- description: |
- Track L1-deposited native currency that is coming into the L2 supply.
- The balance delta is considered to be a "withdrawal" from L1,
- similar to a withdrawal of the Beacon-chain into the Ethereum L1 execution chain.
- globs:
- - "eth/tracers/live/supply.go"
- - title: "Hardware wallet support"
- description: Extend Ledger wallet support for newer devices on Macos
- sub:
- - title: Fix Ledger discoverability
- # upstream PR: https://github.com/ethereum/go-ethereum/pull/28863/
- globs:
- - "accounts/usbwallet/hub.go"
- - title: "Testing"
- description: Additional or modified tests, not already captured by the above diff
- ignore:
- - "**/*_test.go"
+ - "e2e_test/*"
+ - "e2e_test/*/*"
# ignored globally, does not count towards line count
ignore:
diff --git op-geth/go.mod Celo/go.mod
index f9aa6cba52baea229058a30b12edcb45e0572007..f89fe1682fc7afc5a453f9e7b682f782446bea7e 100644
--- op-geth/go.mod
+++ Celo/go.mod
@@ -55,6 +55,7 @@ github.com/mattn/go-isatty v0.0.20
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/protolambda/bls12-381-util v0.1.0
github.com/protolambda/zrnt v0.32.2
github.com/protolambda/ztyp v0.2.2
@@ -133,7 +134,6 @@ github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/opentracing/opentracing-go v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.12.0 // indirect
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
github.com/prometheus/common v0.32.1 // indirect