Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ $constructorData = $contract->bytecode($bytecode)->getData($params);

// get function data
$functionData = $contract->at($contractAddress)->getData($functionName, $params);

//get event log data
//$fromBlock and $toBlock are optional, default to 'latest' and accept block numbers integers
$events = $contract->getEventLogs($eventName, $fromBlock, $toBlock);
```

# Assign value to outside scope(from callback scope to outside scope)
Expand Down
166 changes: 131 additions & 35 deletions src/Contract.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* This file is part of web3.php package.
* This file is part of the web3.php package.
*
* (c) Kuan-Cheng,Lai <alk03073135@gmail.com>
*
Expand All @@ -11,7 +11,8 @@

namespace Web3;

use InvalidArgumentException;
use \InvalidArgumentException;
use \RuntimeException;
use Web3\Providers\Provider;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\RequestManager;
Expand Down Expand Up @@ -175,7 +176,7 @@ public function __construct($provider, $abi, $defaultBlock = 'latest')
// public function __call($name, $arguments)
// {
// if (empty($this->provider)) {
// throw new \RuntimeException('Please set provider first.');
// throw new RuntimeException('Please set provider first.');
// }
// $class = explode('\\', get_class());
// if (preg_match('/^[a-zA-Z0-9]+$/', $name) === 1) {
Expand Down Expand Up @@ -267,6 +268,7 @@ public function setDefaultBlock($defaultBlock)

/**
* getFunctions
* get an array of all methods in the loaded contract
*
* @return array
*/
Expand All @@ -277,6 +279,7 @@ public function getFunctions()

/**
* getEvents
* get an array of all events (and their inputs) in the loaded contract
*
* @return array
*/
Expand Down Expand Up @@ -368,14 +371,15 @@ public function setToAddress($address)

/**
* at
* set the address of the deployed contract to make calls to
*
* @param string $address
* @return $this
*/
public function at($address)
{
if (AddressValidator::validate($address) === false) {
throw new InvalidArgumentException('Please make sure address is valid.');
throw new InvalidArgumentException('Please make sure the contract address is valid.');
}
$this->toAddress = AddressFormatter::format($address);

Expand All @@ -391,7 +395,7 @@ public function at($address)
public function bytecode($bytecode)
{
if (HexValidator::validate($bytecode) === false) {
throw new InvalidArgumentException('Please make sure bytecode is valid.');
throw new InvalidArgumentException('Please make sure the bytecode input is valid.');
}
$this->bytecode = Utils::stripZero($bytecode);

Expand All @@ -407,7 +411,7 @@ public function bytecode($bytecode)
public function abi($abi)
{
if (StringValidator::validate($abi) === false) {
throw new InvalidArgumentException('Please make sure abi is valid.');
throw new InvalidArgumentException('Please make sure the abi input is valid.');
}
$abiArray = [];
if (is_string($abi)) {
Expand Down Expand Up @@ -438,7 +442,7 @@ public function abi($abi)

/**
* new
* Deploy a contruct with params.
* Deploy a new contract, along with any relevant parameters for its constructor.
*
* @param mixed
* @return void
Expand All @@ -452,13 +456,13 @@ public function new()

$input_count = isset($constructor['inputs']) ? count($constructor['inputs']) : 0;
if (count($arguments) < $input_count) {
throw new InvalidArgumentException('Please make sure you have put all constructor params and callback.');
throw new InvalidArgumentException('Please make sure you have included all constructor parameters and a callback function.');
}
if (is_callable($callback) !== true) {
throw new \InvalidArgumentException('The last param must be callback function.');
throw new InvalidArgumentException('The last parameter must be a callback function.');
}
if (!isset($this->bytecode)) {
throw new \InvalidArgumentException('Please call bytecode($bytecode) before new().');
throw new InvalidArgumentException('Please call bytecode($bytecode) before new().');
}
$params = array_splice($arguments, 0, $input_count);
$data = $this->ethabi->encodeParameters($constructor, $params);
Expand All @@ -480,7 +484,8 @@ public function new()

/**
* send
* Send function method.
* Send inputs to a specific method of the deployed contract
* (interacts with chain data and can alter it: costs gas)
*
* @param mixed
* @return void
Expand All @@ -493,7 +498,7 @@ public function send()
$callback = array_pop($arguments);

if (!is_string($method)) {
throw new InvalidArgumentException('Please make sure the method is string.');
throw new InvalidArgumentException('Please make sure the method name is supplied as a string as the first parameter.');
}

$functions = [];
Expand All @@ -503,10 +508,10 @@ public function send()
}
};
if (count($functions) < 1) {
throw new InvalidArgumentException('Please make sure the method exists.');
throw new InvalidArgumentException('Please make sure the named method exists in the contract.');
}
if (is_callable($callback) !== true) {
throw new \InvalidArgumentException('The last param must be callback function.');
throw new InvalidArgumentException('The last parameter must be a callback function.');
}

// check the last one in arguments is transaction object
Expand Down Expand Up @@ -558,7 +563,7 @@ public function send()
break;
}
if (empty($data) || empty($functionName)) {
throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
throw new InvalidArgumentException('Please make sure you have included all parameters of the method and a callback function.');
}
$functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
$transaction['to'] = $this->toAddress;
Expand All @@ -575,7 +580,8 @@ public function send()

/**
* call
* Call function method.
* Call a specific method of the deployed contract
* (read-only, cannot alter chain data: does not cost gas)
*
* @param mixed
* @return void
Expand All @@ -588,7 +594,7 @@ public function call()
$callback = array_pop($arguments);

if (!is_string($method)) {
throw new InvalidArgumentException('Please make sure the method is string.');
throw new InvalidArgumentException('Please make sure the method name is supplied as a string as the first parameter.');
}

$functions = [];
Expand All @@ -598,10 +604,10 @@ public function call()
}
};
if (count($functions) < 1) {
throw new InvalidArgumentException('Please make sure the method exists.');
throw new InvalidArgumentException('Please make sure the named method exists in the contract.');
}
if (is_callable($callback) !== true) {
throw new \InvalidArgumentException('The last param must be callback function.');
throw new InvalidArgumentException('The last parameter must be a callback function.');
}

// check the arguments
Expand All @@ -623,7 +629,7 @@ public function call()
break;
}
if (empty($data) || empty($functionName)) {
throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
throw new InvalidArgumentException('Please make sure you have included all parameters of the method and a callback function.');
}
// remove arguments
array_splice($arguments, 0, $paramsLen);
Expand Down Expand Up @@ -686,13 +692,13 @@ public function estimateGas()
$constructor = $this->constructor;

if (count($arguments) < count($constructor['inputs'])) {
throw new InvalidArgumentException('Please make sure you have put all constructor params and callback.');
throw new InvalidArgumentException('Please make sure you have included all constructor parameters and a callback function.');
}
if (is_callable($callback) !== true) {
throw new \InvalidArgumentException('The last param must be callback function.');
throw new InvalidArgumentException('The last parameter must be a callback function.');
}
if (!isset($this->bytecode)) {
throw new \InvalidArgumentException('Please call bytecode($bytecode) before estimateGas().');
throw new InvalidArgumentException('Please call bytecode($bytecode) before estimateGas().');
}
$params = array_splice($arguments, 0, count($constructor['inputs']));
$data = $this->ethabi->encodeParameters($constructor, $params);
Expand All @@ -706,7 +712,7 @@ public function estimateGas()
$method = array_splice($arguments, 0, 1)[0];

if (!is_string($method)) {
throw new InvalidArgumentException('Please make sure the method is string.');
throw new InvalidArgumentException('Please make sure the method name is supplied as a string as the first parameter.');
}

$functions = [];
Expand All @@ -716,10 +722,10 @@ public function estimateGas()
}
};
if (count($functions) < 1) {
throw new InvalidArgumentException('Please make sure the method exists.');
throw new InvalidArgumentException('Please make sure the named method exists in the contract.');
}
if (is_callable($callback) !== true) {
throw new \InvalidArgumentException('The last param must be callback function.');
throw new InvalidArgumentException('The last parameter must be a callback function.');
}

// check the last one in arguments is transaction object
Expand Down Expand Up @@ -771,7 +777,7 @@ public function estimateGas()
break;
}
if (empty($data) || empty($functionName)) {
throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
throw new InvalidArgumentException('Please make sure you have included all parameters of the method and a callback function.');
}
$functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
$transaction['to'] = $this->toAddress;
Expand All @@ -789,9 +795,9 @@ public function estimateGas()

/**
* getData
* Get the function method call data.
* With this function, you can send signed contract function transaction.
* 1. Get the funtion data with params.
* Get the contract method's call data.
* With this function, you can send signed contract method transactions.
* 1. Get the method data with parameters.
* 2. Sign the data with user private key.
* 3. Call sendRawTransaction.
*
Expand All @@ -808,10 +814,10 @@ public function getData()
$constructor = $this->constructor;

if (count($arguments) < count($constructor['inputs'])) {
throw new InvalidArgumentException('Please make sure you have put all constructor params and callback.');
throw new InvalidArgumentException('Please make sure you have included all constructor parameters and a callback function.');
}
if (!isset($this->bytecode)) {
throw new \InvalidArgumentException('Please call bytecode($bytecode) before getData().');
throw new InvalidArgumentException('Please call bytecode($bytecode) before getData().');
}
$params = array_splice($arguments, 0, count($constructor['inputs']));
$data = $this->ethabi->encodeParameters($constructor, $params);
Expand All @@ -820,7 +826,7 @@ public function getData()
$method = array_splice($arguments, 0, 1)[0];

if (!is_string($method)) {
throw new InvalidArgumentException('Please make sure the method is string.');
throw new InvalidArgumentException('Please make sure the method name is supplied as a string as the first parameter.');
}

$functions = [];
Expand All @@ -830,7 +836,7 @@ public function getData()
}
};
if (count($functions) < 1) {
throw new InvalidArgumentException('Please make sure the method exists.');
throw new InvalidArgumentException('Please make sure the named method exists in the contract.');
}

$params = $arguments;
Expand All @@ -849,12 +855,102 @@ public function getData()
break;
}
if (empty($data) || empty($functionName)) {
throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
throw new InvalidArgumentException('Please make sure you have included all parameters of the method and a callback function.');
}
$functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
$functionData = Utils::stripZero($functionSignature) . Utils::stripZero($data);
}
return $functionData;
}
}

/**
* getEventLogs
*
* @param string $eventName
* @param string|int $fromBlock
* @param string|int $toBlock
* @return array
*/
public function getEventLogs(string $eventName, $fromBlock = 'latest', $toBlock = 'latest')
{
//try to ensure block numbers are valid together
if ($fromBlock !== 'latest') {
if (!is_int($fromBlock) || $fromBlock < 1) {
throw new InvalidArgumentException('Please make sure fromBlock is a valid block number');
} else if ($toBlock !== 'latest' && $fromBlock > $toBlock) {
throw new InvalidArgumentException('Please make sure fromBlock is equal or less than toBlock');
}
}

if ($toBlock !== 'latest') {
if (!is_int($toBlock) || $toBlock < 1) {
throw new InvalidArgumentException('Please make sure toBlock is a valid block number');
} else if ($fromBlock === 'latest') {
throw new InvalidArgumentException('Please make sure toBlock is equal or greater than fromBlock');
}
}

$eventLogData = [];

//ensure the event actually exists before trying to filter for it
if (!array_key_exists($eventName, $this->events)) {
throw new InvalidArgumentException("'{$eventName}' does not exist in the ABI for this contract");
}

//indexed and non-indexed event parameters must be treated separately
//indexed parameters are stored in the 'topics' array
//non-indexed parameters are stored in the 'data' value
$eventParameterNames = [];
$eventParameterTypes = [];
$eventIndexedParameterNames = [];
$eventIndexedParameterTypes = [];

foreach ($this->events[$eventName]['inputs'] as $input) {
if ($input['indexed']) {
$eventIndexedParameterNames[] = $input['name'];
$eventIndexedParameterTypes[] = $input['type'];
} else {
$eventParameterNames[] = $input['name'];
$eventParameterTypes[] = $input['type'];
}
}

$numEventIndexedParameterNames = count($eventIndexedParameterNames);

//filter through log data to find any logs which match this event (topic) from
//this contract, between these specified blocks (defaulting to the latest block only)
$this->eth->getLogs([
'fromBlock' => (is_int($fromBlock)) ? '0x' . dechex($fromBlock) : $fromBlock,
'toBlock' => (is_int($toBlock)) ? '0x' . dechex($toBlock) : $toBlock,
'topics' => [$this->ethabi->encodeEventSignature($this->events[$eventName])],
'address' => $this->toAddress
],
function ($error, $result) use (&$eventLogData, $eventParameterTypes, $eventParameterNames, $eventIndexedParameterTypes, $eventIndexedParameterNames) {
if ($error !== null) {
throw new RuntimeException($error->getMessage());
}

foreach ($result as $object) {
//decode the data from the log into the expected formats, with its corresponding named key
$decodedData = array_combine($eventParameterNames, $this->ethabi->decodeParameters($eventParameterTypes, $object->data));

//decode the indexed parameter data
for ($i = 0; $i < $numEventIndexedParameterNames; $i++) {
Copy link
Copy Markdown

@davorminchorov davorminchorov Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$numEventIndexedParameterNames is not defined anywhere.

//topics[0] is the event signature, so we start from $i + 1 for the indexed parameter data
$decodedData[$eventIndexedParameterNames[$i]] = $this->ethabi->decodeParameters([$eventIndexedParameterTypes[$i]], $object->topics[$i + 1])[0];
}

//include block metadata for context, along with event data
$eventLogData[] = [
'transactionHash' => $object->transactionHash,
'blockHash' => $object->blockHash,
'blockNumber' => hexdec($object->blockNumber),
'data' => $decodedData
];
}
});

return $eventLogData;
}
}
Loading