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.

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'

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 = &params.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)

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 +}
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
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(&params.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
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 = &params.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 := &params.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 := &params.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 &params.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 }
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
(deleted)
(binary file)
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 + } + } +}
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