Skip to content

Commit

Permalink
Added support for reading from smart contracts (#207)
Browse files Browse the repository at this point in the history
### What changed? Why?
- Added a function to read from a smart contract.

### Testing
- Added unit tests
- Ran an e2e test locally
```
pureInt16: -32768
pureUint16: 65535
pureUint256: 115792089237316195423570985008687907853269984665640564039457584007913129639935
pureInt256: -57896044618658097711785492504343953926634992332820282019728792003956564819968
pureUint128: 340282366920938463463374607431768211455
pureUint64: 18446744073709551615
pureUint32: 4294967295
pureBool: true
pureAddress: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
exampleFunction: false
pureArray: [1, 2, 3]
pureBytes: 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003748656c6c6f2c20576f726c64212048656c6c6f2c20576f726c64212048656c6c6f2c20576f726c64212048656c6c6f2c20576f726c6421000000000000000000
pureBytes1: 0xff
pureNestedStruct: {"a":1,"nestedFields":{"nestedArray":{"a":[0,0,0]},"a":2}}
overload (x: 1, y: 2): [0, 0, 0]
overload (no args): 1
overload (address arg): 1
```

- Will fast-follow with e2e tests after releasing and wrapping up other
work

---------

Co-authored-by: Alexander Stone <[email protected]>
  • Loading branch information
rohan-agarwal-coinbase and alex-stone authored Oct 23, 2024
1 parent 23dba84 commit dc866ec
Show file tree
Hide file tree
Showing 3 changed files with 501 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Include ERC20 and ERC721 token transfer information into transaction content.
- Add support for reading from smart contracts.

## [0.7.0] - 2024-09-26

Expand Down
95 changes: 95 additions & 0 deletions lib/coinbase/smart_contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def self.create_nft_contract(
new(contract)
end

# Creates a new ERC1155 multi-token contract, that can subsequently be deployed to
# the blockchain.
# @param address_id [String] The address ID of deployer
# @param wallet_id [String] The wallet ID of the deployer
# @param uri [String] The URI for the token metadata
# @return [SmartContract] The new ERC1155 Multi-Token SmartContract object
def self.create_multi_token_contract(
address_id:,
wallet_id:,
Expand All @@ -126,6 +132,95 @@ def self.create_multi_token_contract(
new(contract)
end

# Reads data from a deployed smart contract.
#
# @param network [Coinbase::Network, Symbol] The Network or Network ID of the Asset
# @param contract_address [String] The address of the deployed contract
# @param method [String] The name of the method to call on the contract
# @param abi [Array, nil] The ABI of the contract. If nil, the method will attempt to use a cached ABI
# @param args [Hash] The arguments to pass to the contract method.
# The keys should be the argument names, and the values should be the argument values.
# @return [Object] The result of the contract call, converted to an appropriate Ruby type
# @raise [Coinbase::ApiError] If there's an error in the API call
def self.read(
network:,
contract_address:,
method:,
abi: nil,
args: {}
)
network = Coinbase::Network.from_id(network)

response = Coinbase.call_api do
smart_contracts_api.read_contract(
network.normalized_id,
contract_address,
{
method: method,
args: (args || {}).to_json,
abi: abi&.to_json
}
)
end

convert_solidity_value(response)
end

# Converts a Solidity value to an appropriate Ruby type.
#
# @param solidity_value [Coinbase::Client::SolidityValue] The Solidity value to convert
# @return [Object] The converted Ruby value
# @raise [ArgumentError] If an unsupported Solidity type is encountered
#
# This method handles the following Solidity types:
# - Integers (uint8, uint16, uint32, uint64, uint128, uint256, int8, int16, int32, int64, int128, int256)
# - Address
# - String
# - Bytes (including fixed-size byte arrays)
# - Boolean
# - Array
# - Tuple (converted to a Hash)
#
# For complex types like arrays and tuples, the method recursively converts nested values.
def self.convert_solidity_value(solidity_value)
return nil if solidity_value.nil?

type = solidity_value.type
value = solidity_value.value
values = solidity_value.values

case type
when 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'int8', 'int16', 'int32', 'int64', 'int128', 'int256'
value&.to_i
when 'address', 'string', /^bytes/
value
when 'bool'
if value.is_a?(String)
value == 'true'
else
!value.nil?
end
when 'array'
values ? values.map { |v| convert_solidity_value(v) } : []
when 'tuple'
if values
result = {}
values.each do |v|
raise ArgumentError, 'Error: Tuple value without a name' unless v.respond_to?(:name)

result[v.name] = convert_solidity_value(v)
end
result
else
{}
end
else
raise ArgumentError, "Unsupported Solidity type: #{type}"
end
end
private_class_method :convert_solidity_value

def self.contract_events_api
Coinbase::Client::ContractEventsApi.new(Coinbase.configuration.api_client)
end
Expand Down
Loading

0 comments on commit dc866ec

Please sign in to comment.