diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 26d1df36..54634bf6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,7 +35,7 @@ jobs: uses: github/codeql-action/analyze@v3 - uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: '3.3' bundler-cache: true - name: "Run rufo code formatting checks" run: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4e53f786..f33819c5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,14 +13,14 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: '3.3' bundler-cache: true - name: Run Yard Doc run: | gem install yard yard doc - name: Deploy GH Pages - uses: JamesIves/github-pages-deploy-action@v4.5.0 + uses: JamesIves/github-pages-deploy-action@v4.6.1 with: branch: gh-pages folder: doc/ diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 1b54754c..ae9c6daa 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -18,8 +18,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, macos-12] - ruby: ['3.0', '3.1', '3.2'] + os: [ubuntu-latest, macos-latest] + ruby: ['3.1', '3.2', '3.3'] steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 @@ -49,5 +49,9 @@ jobs: run: | bundle exec rspec env: - COVERAGE: true INFURA_TOKEN: ${{ secrets.INFURA_TOKEN }} + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/lib/eth/abi/decoder.rb b/lib/eth/abi/decoder.rb index 5f104c7f..27911a60 100644 --- a/lib/eth/abi/decoder.rb +++ b/lib/eth/abi/decoder.rb @@ -51,6 +51,25 @@ def type(type, arg) type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32]) end end + elsif type.base_type == 'tuple' + offset = 0 + data = {} + type.components.each do |c| + if c.dynamic? + pointer = Util.deserialize_big_endian_to_int arg[offset, 32] # Pointer to the size of the array's element + data_len = Util.deserialize_big_endian_to_int arg[pointer, 32] # length of the element + + data[c.name] = type(c, arg[pointer, Util.ceil32(data_len) + 32]) + # puts data + offset += 32 + else + size = c.size + data[c.name] = type(c, arg[offset, size]) + offset += size + # puts data + end + end + data elsif type.dynamic? l = Util.deserialize_big_endian_to_int arg[0, 32] nested_sub = type.nested_sub diff --git a/lib/eth/abi/event.rb b/lib/eth/abi/event.rb index a3d07ff1..d61b429d 100644 --- a/lib/eth/abi/event.rb +++ b/lib/eth/abi/event.rb @@ -126,9 +126,20 @@ def decode_logs(interfaces, logs) def decode_log(inputs, data, topics, anonymous = false) topic_inputs, data_inputs = inputs.partition { |i| i["indexed"] } - topic_types = topic_inputs.map { |i| i["type"] } - data_types = data_inputs.map { |i| i["type"] } - + topic_types = topic_inputs.map do |i| + if i['type'] == 'tuple' + Type.parse(i['type'], i['components'], i['name']) + else + i['type'] + end + end + data_types = data_inputs.map do |i| + if i['type'] == 'tuple' + Type.parse(i['type'], i['components'], i['name']) + else + i['type'] + end + end # If event is anonymous, all topics are arguments. Otherwise, the first # topic will be the event signature. if anonymous == false diff --git a/lib/eth/abi/type.rb b/lib/eth/abi/type.rb index 62b862cd..94ea4013 100644 --- a/lib/eth/abi/type.rb +++ b/lib/eth/abi/type.rb @@ -71,7 +71,15 @@ def initialize(base_type, sub_type, dimensions, components = nil, component_name # @return [Eth::Abi::Type] a parsed Type object. # @raise [ParseError] if it fails to parse the type. def parse(type, components = nil, component_name = nil) - return type if type.is_a?(Type) + if type.is_a?(Type) + @base_type = type.base_type + @sub_type = type.sub_type + @dimensions = type.dimensions + @components = type.components + @name = type.name + return + end + _, base_type, sub_type, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a # type dimension can only be numeric diff --git a/lib/eth/api.rb b/lib/eth/api.rb index 2b6488b9..fe650154 100644 --- a/lib/eth/api.rb +++ b/lib/eth/api.rb @@ -115,7 +115,6 @@ module Api "eth_blockNumber", "eth_call", "eth_chainId", - "eth_coinbase", "eth_compileLLL", "eth_compileSerpent", "eth_compileSolidity", diff --git a/lib/eth/client.rb b/lib/eth/client.rb index b75bccad..ecd9dff6 100644 --- a/lib/eth/client.rb +++ b/lib/eth/client.rb @@ -25,7 +25,7 @@ class Client # The connected network's chain ID. attr_reader :chain_id - # The connected network's client coinbase. + # The connected network's client default account. attr_accessor :default_account # The default transaction max priority fee per gas in Wei, defaults to {Tx::DEFAULT_PRIORITY_FEE}. @@ -62,15 +62,15 @@ def initialize(_) @max_fee_per_gas = Tx::DEFAULT_GAS_PRICE end - # Gets the default account (coinbase) of the connected client. + # Gets the default account (first account) of the connected client. # # **Note**, that many remote providers (e.g., Infura) do not provide # any accounts. # - # @return [Eth::Address] the coinbase account address. + # @return [Eth::Address] the default account address. def default_account raise ArgumentError, "The default account is not available on remote connections!" unless local? || @default_account - @default_account ||= Address.new eth_coinbase["result"] + @default_account ||= Address.new eth_accounts["result"].first end # Gets the chain ID of the connected network. @@ -108,7 +108,7 @@ def resolve_ens(ens_name, registry = Ens::DEFAULT_ADDRESS, coin_type = Ens::Coin end # Simply transfer Ether to an account and waits for it to be mined. - # Uses `eth_coinbase` and external signer if no sender key is + # Uses `eth_accounts` and external signer if no sender key is # provided. # # See {#transfer} for params and overloads. @@ -119,7 +119,7 @@ def transfer_and_wait(destination, amount, **kwargs) end # Simply transfer Ether to an account without any call data or - # access lists attached. Uses `eth_coinbase` and external signer + # access lists attached. Uses `eth_accounts` and external signer # if no sender key is provided. # # **Note**, that many remote providers (e.g., Infura) do not provide @@ -179,7 +179,7 @@ def transfer_erc20(erc20_contract, destination, amount, **kwargs) end # Deploys a contract and waits for it to be mined. Uses - # `eth_coinbase` or external signer if no sender key is provided. + # `eth_accounts` or external signer if no sender key is provided. # # See {#deploy} for params and overloads. # @@ -190,7 +190,7 @@ def deploy_and_wait(contract, *args, **kwargs) contract.address = Address.new(addr).to_s end - # Deploys a contract. Uses `eth_coinbase` or external signer + # Deploys a contract. Uses `eth_accounts` or external signer # if no sender key is provided. # # **Note**, that many remote providers (e.g., Infura) do not provide diff --git a/lib/eth/contract/event.rb b/lib/eth/contract/event.rb index 999e2bc0..60233ee4 100644 --- a/lib/eth/contract/event.rb +++ b/lib/eth/contract/event.rb @@ -26,7 +26,9 @@ class Contract::Event # @param data [Hash] contract event data. def initialize(data) @name = data["name"] - @input_types = data["inputs"].collect { |x| x["type"] } + @input_types = data["inputs"].collect do |x| + type_name x + end @inputs = data["inputs"].collect { |x| x["name"] } @event_string = Abi::Event.signature(data) @signature = Digest::Keccak.hexdigest(@event_string, 256) @@ -38,5 +40,16 @@ def initialize(data) def set_address(address) @address = address.nil? ? nil : Eth::Address.new(address).address end + + private + def type_name(x) + type = x["type"] + case type + when "tuple" + "(#{x['components'].collect { |c| type_name(c) }.join(',')})" + else + type + end + end end end diff --git a/spec/eth/abi/event_spec.rb b/spec/eth/abi/event_spec.rb index fe22ba4d..19ed435f 100644 --- a/spec/eth/abi/event_spec.rb +++ b/spec/eth/abi/event_spec.rb @@ -247,6 +247,7 @@ expect(signature).to eq "Transfer(address,address,uint256)" end + it "generates transfer function signature" do abi = erc20_abi.find { |i| i["type"] == "function" && i["name"] == "transfer" } signature = Abi::Event.signature(abi) diff --git a/spec/eth/contract/event_spec.rb b/spec/eth/contract/event_spec.rb index 043f0032..f5e78c91 100644 --- a/spec/eth/contract/event_spec.rb +++ b/spec/eth/contract/event_spec.rb @@ -13,5 +13,85 @@ expect(contract.events[1].signature).to eq("1f3a0e41bf4d8306f04763663bf025d1824a391571ce3a07186a195f8c4cfd3c") expect(contract.events[1].event_string).to eq("killed()") end + + it "generates signature for event with tuple params" do + event = Eth::Contract::Event.new({ + "anonymous" => false, + "inputs" => [ + { + "components" => [ + { + "internalType" => "uint256", + "name" => "topicId", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "proposalId", + "type" => "uint256" + }, + { + "internalType" => "string", + "name" => "name", + "type" => "string" + }, + { + "internalType" => "string", + "name" => "symbol", + "type" => "string" + }, + { + "internalType" => "uint256", + "name" => "duration", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "totalSupply", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "miniStakeValue", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "maxStakeValue", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "maxParticipants", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "whitelistIndex", + "type" => "uint256" + }, + { + "internalType" => "address", + "name" => "proposer", + "type" => "address" + }, + { + "internalType" => "bool", + "name" => "useWhitelist", + "type" => "bool" + } + ], + "indexed" => false, + "internalType" => "struct VoteContract.ProposalCreatedParams", + "name" => "params", + "type" => "tuple" + } + ], + "name" => "ProposalCreated", + "type" => "event" + }) + expect(event.event_string).to eq('ProposalCreated((uint256,uint256,string,string,uint256,uint256,uint256,uint256,uint256,uint256,address,bool))') + expect(event.signature).to eq('4449031b77cbe261580701c097fb63211e768f685581e616330dfff20493536c') + end end end diff --git a/spec/eth/solidity_spec.rb b/spec/eth/solidity_spec.rb index 4663f737..106eb2b3 100644 --- a/spec/eth/solidity_spec.rb +++ b/spec/eth/solidity_spec.rb @@ -34,7 +34,8 @@ result = solc.compile contract expect(result["DepositContract"]).to be payload = result["DepositContract"]["bin"] - expect(payload).to start_with "604060808152" + expect(payload).to start_with "60" + expect(payload).to end_with "33" params = { from: geth.default_account, priority_fee: 0,