Skip to content

Commit

Permalink
Psdk 483 payable contract invocation (#184)
Browse files Browse the repository at this point in the history
### What changed? Why?
- Support optionally setting `amount` for payable smart contract method
invocations
- Bump openapi generated files to use latest generator version `7.8.0`

#### Qualified Impact
<!-- Please evaluate what components could be affected and what the
impact would be if there was an
error. How would this error be resolved, e.g. rollback a deploy, push a
new fix, disable a feature
flag, etc... -->
  • Loading branch information
John-peterson-coinbase authored Sep 11, 2024
1 parent 33c9f50 commit 845a629
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### Added

- Add support for Arbitrum-Mainnet
- Add optional arguments to allow setting amount for payable contract method invocations

## [0.3.0] - 2024-09-05

Expand Down
12 changes: 10 additions & 2 deletions lib/coinbase/address/wallet_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,25 @@ def trade(amount, from_asset_id, to_asset_id)
# @param method [String] The method to invoke on the contract.
# @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.
# @param amount [Integer, Float, BigDecimal] (Optional) The amount of the native Asset
# to send to a payable contract method.
# @param asset_id [Symbol] (Optional) The ID of the Asset to send to a payable contract method.
# The Asset must be a denomination of the native Asset. For Ethereum, :eth, :gwei, and :wei are supported.
# @return [Coinbase::ContractInvocation] The contract invocation object.
def invoke_contract(contract_address:, abi:, method:, args:)
def invoke_contract(contract_address:, abi:, method:, args:, amount: nil, asset_id: nil)
ensure_can_sign!
ensure_sufficient_balance!(amount, asset_id) if amount && asset_id

invocation = ContractInvocation.create(
address_id: id,
wallet_id: wallet_id,
contract_address: contract_address,
abi: abi,
method: method,
args: args
args: args,
amount: amount,
asset_id: asset_id,
network: network
)

# If a server signer is managing keys, it will sign and broadcast the underlying transaction out of band.
Expand Down
27 changes: 26 additions & 1 deletion lib/coinbase/contract_invocation.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'bigdecimal'

module Coinbase
# A representation of a Contract Invocation.
class ContractInvocation
Expand All @@ -10,6 +12,11 @@ class << self
# @param contract_address [String] The contract address
# @param abi [Array<Hash>] The contract ABI
# @param method [String] The contract method
# @param amount [Integer, Float, BigDecimal] The amount of the native Asset
# to send to a payable contract method.
# @param asset_id [Symbol] The ID of the Asset to send to a payable contract method.
# The Asset must be a denomination of the native Asset. For Ethereum, :eth, :gwei, and :wei are supported.
# @param network [Coinbase::Network, Symbol] The Network or Network ID of the Asset
# @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 [ContractInvocation] The new Contract Invocation object
Expand All @@ -20,16 +27,28 @@ def create(
contract_address:,
abi:,
method:,
amount:,
asset_id:,
network:,
args: {}
)
atomic_amount = nil

if amount && asset_id && network
network = Coinbase::Network.from_id(network)
asset = network.get_asset(asset_id)
atomic_amount = asset.to_atomic_amount(amount).to_i_to_s
end

model = Coinbase.call_api do
contract_invocation_api.create_contract_invocation(
wallet_id,
address_id,
contract_address: contract_address,
abi: abi.to_json,
method: method,
args: args.to_json
args: args.to_json,
amount: atomic_amount
)
end

Expand Down Expand Up @@ -121,6 +140,12 @@ def args
JSON.parse(@model.args).transform_keys(&:to_sym)
end

# Returns the amount of the native asset sent to a payable contract method, if applicable.
# @return [BigDecimal] The amount in atomic units of the native asset
def amount
BigDecimal(@model.amount)
end

# Returns the transaction.
# @return [Coinbase::Transaction] The Transfer transaction
def transaction
Expand Down
2 changes: 2 additions & 0 deletions spec/factories/contract_invocation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
transient do
key { build(:key) }
status { 'pending' }
amount { '0' }
end

address_id { key.address.to_s }
Expand Down Expand Up @@ -54,6 +55,7 @@
network { nil }
status { nil }
key { build(:key) }
amount { '0' }
end

model { build(:contract_invocation_model, key: key) }
Expand Down
53 changes: 52 additions & 1 deletion spec/unit/coinbase/address/wallet_address_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,16 @@
contract_invocation
end

it 'creates a contract invocation' do
it 'creates a contract invocation' do # rubocop:disable RSpec/ExampleLength
expect(Coinbase::ContractInvocation).to have_received(:create).with(
address_id: address_id,
wallet_id: wallet_id,
contract_address: contract_invocation_model.contract_address,
method: contract_invocation_model.method,
abi: abi,
amount: nil,
asset_id: nil,
network: network,
args: args
)
end
Expand Down Expand Up @@ -171,6 +174,54 @@
end
end
end

context 'when invoking a payable contract method' do
subject(:contract_invocation) do
address.invoke_contract(
contract_address: contract_invocation_model.contract_address,
method: contract_invocation_model.method,
abi: abi,
args: args,
amount: 100,
asset_id: :wei
)
end

let(:balance) { 1_000 }
let(:created_invocation) { build(:contract_invocation, network_id, key: key, amount: '100') }

before do
allow(addresses_api)
.to receive(:get_external_address_balance)
.with(normalized_network_id, address_id, 'eth')
.and_return(build(:balance_model, network_id, whole_amount: balance))

allow(Coinbase::ContractInvocation).to receive(:create).and_return(created_invocation)

allow(created_invocation).to receive(:sign)
allow(created_invocation).to receive(:broadcast!)

contract_invocation
end

it 'creates a contract invocation' do # rubocop:disable RSpec/ExampleLength
expect(Coinbase::ContractInvocation).to have_received(:create).with(
address_id: address_id,
wallet_id: wallet_id,
contract_address: contract_invocation_model.contract_address,
method: contract_invocation_model.method,
abi: abi,
amount: 100,
asset_id: :wei,
network: network,
args: args
)
end

it 'returns the created contract invocation' do
expect(contract_invocation).to eq(created_invocation)
end
end
end

describe '#transfer' do
Expand Down
13 changes: 12 additions & 1 deletion spec/unit/coinbase/contract_invocation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
contract_address: contract_address,
abi: abi.to_json,
method: method,
amount: '0',
args: args.to_json,
transaction: transaction_model
)
Expand All @@ -61,6 +62,9 @@
contract_address: contract_address,
abi: abi,
method: method,
amount: nil,
asset_id: nil,
network: network,
args: args
)
end
Expand All @@ -70,7 +74,8 @@
contract_address: contract_address,
abi: abi.to_json,
method: method,
args: args.to_json
args: args.to_json,
amount: nil
}
end

Expand Down Expand Up @@ -179,6 +184,12 @@
end
end

describe '#amount' do
it 'returns the Amount' do
expect(contract_invocation.amount).to eq(BigDecimal(0))
end
end

describe '#status' do
it 'returns the transaction status' do
expect(contract_invocation.status).to eq(Coinbase::Transaction::Status::PENDING)
Expand Down

0 comments on commit 845a629

Please sign in to comment.