Skip to content

Commit

Permalink
Enable passing in constructor params to deploy (#106)
Browse files Browse the repository at this point in the history
* Add missing def_delegator for constructor_inputs

* Enable passing in constructor to deploy contracts

* Call greeter function with params

* Update greeter.sol

Co-authored-by: Bea <[email protected]>
Co-authored-by: Yuta Kurotaki <[email protected]>
  • Loading branch information
3 people authored May 29, 2022
1 parent 33089f7 commit bb67fea
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 23 deletions.
49 changes: 28 additions & 21 deletions lib/eth/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,14 @@ def transfer(destination, amount, sender_key = nil, legacy = false)
#
# @overload deploy(contract)
# @param contract [Eth::Contract] contracts to deploy.
# @overload deploy(contract, sender_key)
# @overload deploy(contract, *args, **kwargs)
# @param contract [Eth::Contract] contracts to deploy.
# @param sender_key [Eth::Key] the sender private key.
# @overload deploy(contract, sender_key, legacy)
# @param contract [Eth::Contract] contracts to deploy.
# @param sender_key [Eth::Key] the sender private key.
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
# *args Optional variable constructor parameter list
# **sender_key [Eth::Key] the sender private key.
# **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
# @return [String] the contract address.
def deploy_and_wait(contract, sender_key: nil, legacy: false)
hash = wait_for_tx(deploy(contract, sender_key: sender_key, legacy: legacy))
def deploy_and_wait(contract, *args, **kwargs)
hash = wait_for_tx(deploy(contract, *args, **kwargs))
addr = eth_get_transaction_receipt(hash)["result"]["contractAddress"]
contract.address = Address.new(addr).to_s
end
Expand All @@ -173,25 +171,28 @@ def deploy_and_wait(contract, sender_key: nil, legacy: false)
#
# @overload deploy(contract)
# @param contract [Eth::Contract] contracts to deploy.
# @overload deploy(contract, sender_key)
# @param contract [Eth::Contract] contracts to deploy.
# @param sender_key [Eth::Key] the sender private key.
# @overload deploy(contract, sender_key, legacy)
# @overload deploy(contract, *args, **kwargs)
# @param contract [Eth::Contract] contracts to deploy.
# @param sender_key [Eth::Key] the sender private key.
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
# *args Optional variable constructor parameter list
# **sender_key [Eth::Key] the sender private key.
# **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
# @return [String] the transaction hash.
# @raise [ArgumentError] in case the contract does not have any source.
def deploy(contract, sender_key: nil, legacy: false)
def deploy(contract, *args, **kwargs)
raise ArgumentError, "Cannot deploy contract without source or binary!" if contract.bin.nil?
raise ArgumentError, "Missing contract constructor params!" if contract.constructor_inputs.length != args.length
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
data = contract.bin
unless args.empty?
data += encode_constructor_params(contract, args)
end
params = {
value: 0,
gas_limit: gas_limit,
chain_id: chain_id,
data: contract.bin,
data: data,
}
if legacy
if kwargs[:legacy]
params.merge!({
gas_price: max_fee_per_gas,
})
Expand All @@ -201,14 +202,14 @@ def deploy(contract, sender_key: nil, legacy: false)
max_gas_fee: max_fee_per_gas,
})
end
unless sender_key.nil?
unless kwargs[:sender_key].nil?
# use the provided key as sender and signer
params.merge!({
from: sender_key.address,
nonce: get_nonce(sender_key.address),
from: kwargs[:sender_key].address,
nonce: get_nonce(kwargs[:sender_key].address),
})
tx = Eth::Tx.new(params)
tx.sign sender_key
tx.sign kwargs[:sender_key]
return eth_send_raw_transaction(tx.hex)["result"]
else
# use the default account as sender and external signer
Expand Down Expand Up @@ -433,6 +434,12 @@ def call_payload(fun, args)
"0x" + fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str)
end

# Encodes constructor params
def encode_constructor_params(contract, args)
types = contract.constructor_inputs.map { |input| input.type }
Util.bin_to_hex(Eth::Abi.encode(types, args))
end

# Prepares parameters and sends the command to the client.
def send_command(command, args)
args << "latest" if ["eth_getBalance", "eth_call"].include? command
Expand Down
30 changes: 29 additions & 1 deletion spec/eth/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,15 @@
expect(address).to start_with "0x"
end

it "deploy the contract with constructor params" do
contract = Contract.from_file(file: "spec/fixtures/contracts/greeter.sol", contract_index: 0)
address = geth_dev_http.deploy_and_wait(contract, "Hello!")
expect(address).to start_with "0x"
end

it "can deploy and call an ens registry" do
ens_registry = Contract.from_bin(bin: ens_registry_bin.strip, abi: ens_registry_abi.strip, name: "ENSRegistryWithFallback")
ens_address = geth_dev_ipc.deploy_and_wait(ens_registry)
ens_address = geth_dev_ipc.deploy_and_wait(ens_registry, "0x112234455c3a32fd11230c42e7bccd4a84e02010")
expect(ens_registry).to be_instance_of(Eth::Contract::ENSRegistryWithFallback)
expect(ens_registry.address).to eq Address.new(ens_address).to_s
expect(geth_dev_ipc.call(ens_registry, "old")).to eq "0x112234455c3a32fd11230c42e7bccd4a84e02010"
Expand Down Expand Up @@ -145,6 +151,13 @@
response = geth_dev_http.call(test_contract, "get")
expect(response).to eq([12, 24])
end

it "calls the function with constructor params" do
contract = Contract.from_file(file: "spec/fixtures/contracts/greeter.sol", contract_index: 0)
address = geth_dev_http.deploy_and_wait(contract, "Hello!")
result = geth_dev_http.call(contract, "greet", address: address)
expect(result).to eq("Hello!")
end
end

describe ".transact .transact_and_wait" do
Expand All @@ -160,6 +173,14 @@
expect(response).to eq(42)
end

it "the value can be set with the set function, overwriting constructor params" do
contract = Contract.from_file(file: "spec/fixtures/contracts/greeter.sol", contract_index: 0)
address = geth_dev_http.deploy_and_wait(contract, "Hello!")
geth_dev_http.transact_and_wait(contract, "setGreeting", "How are you?", address: address)
response = geth_dev_http.call(contract, "greet")
expect(response).to eq("How are you?")
end

it "transact the function with key" do
geth_dev_http.transfer_and_wait(test_key.address, 1337 * Unit::ETHER)
address = geth_dev_http.deploy_and_wait(contract, sender_key: test_key)
Expand All @@ -172,6 +193,13 @@
response = geth_dev_http.transact_and_wait(contract, "set", 42, legacy: true, address: address)
expect(response).to start_with "0x"
end

it "transacts the function with constructor params" do
contract = Contract.from_file(file: "spec/fixtures/contracts/greeter.sol", contract_index: 0)
address = geth_dev_http.deploy_and_wait(contract, "Hello!")
response = geth_dev_http.transact_and_wait(contract, "setGreeting", "How are you?", address: address)
expect(response).to start_with "0x"
end
end

describe ".is_valid_signature" do
Expand Down
2 changes: 1 addition & 1 deletion spec/eth/solidity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
result = solc.compile contract
expect(result.keys).to eq ["Greeter", "Mortal"]
expect(result["Mortal"]["abi"]).to eq JSON.parse '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"}]'
expect(result["Greeter"]["abi"]).to eq JSON.parse '[{"inputs":[{"internalType":"string","name":"message","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"}]'
expect(result["Greeter"]["abi"]).to eq JSON.parse '[{"inputs":[{"internalType":"string","name":"message","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type": "function"}]'
expect(result["Mortal"]["bin"]).to start_with "6080604052348015600f57600080fd5b5060"
expect(result["Greeter"]["bin"]).to start_with "608060405234801561001057600080fd5b5060"
end
Expand Down
4 changes: 4 additions & 0 deletions spec/fixtures/contracts/greeter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ contract Greeter is Mortal {
greeting = message;
}

function setGreeting(string memory message) public {
greeting = message;
}

// call the greeting from the contract
function greet() public view returns (string memory) {
return greeting;
Expand Down

0 comments on commit bb67fea

Please sign in to comment.