-
Notifications
You must be signed in to change notification settings - Fork 550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementation of Contract::getEventLogs() and improved general comments and error messages #196
Open
Nanolucas
wants to merge
20
commits into
web3p:master
Choose a base branch
from
Nanolucas:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
dd25610
added and improved comments and error messages for clarity
Nanolucas fd1c09b
setting composer package name to run my own package
Nanolucas 56b50e6
change install package in readme
Nanolucas bffe4cf
undo previous changes, using VCS package instead
Nanolucas 486d849
make exception namespacing consistent
Nanolucas 665b15b
update variable name for consistency
Nanolucas af71b4d
implement event log getter
Nanolucas 121c80f
update $eventInputParametersStringArray
Nanolucas af3bd4c
remove new function before sending pull request
Nanolucas 816c223
working implementation of getEventLogs
Nanolucas aade350
return data with named keys
Nanolucas 125aea0
include block metadata in returned array
Nanolucas 46dc78e
handle indexed parameters
Nanolucas bb9f9f0
added comment
Nanolucas b26da9a
initialise empty array
Nanolucas 91ef36f
add key check to prevent PHP notice
Nanolucas d4e9e4a
throw an exception if event name isn't found
Nanolucas 935e919
return the transactionHash from event data
Nanolucas 87f8976
minor efficiency improvements
Nanolucas 0377c6c
add contract::getEventLogs
Nanolucas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 <[email protected]> | ||
* | ||
|
@@ -11,7 +11,8 @@ | |
|
||
namespace Web3; | ||
|
||
use InvalidArgumentException; | ||
use \InvalidArgumentException; | ||
use \RuntimeException; | ||
use Web3\Providers\Provider; | ||
use Web3\Providers\HttpProvider; | ||
use Web3\RequestManagers\RequestManager; | ||
|
@@ -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) { | ||
|
@@ -267,6 +268,7 @@ public function setDefaultBlock($defaultBlock) | |
|
||
/** | ||
* getFunctions | ||
* get an array of all methods in the loaded contract | ||
* | ||
* @return array | ||
*/ | ||
|
@@ -277,6 +279,7 @@ public function getFunctions() | |
|
||
/** | ||
* getEvents | ||
* get an array of all events (and their inputs) in the loaded contract | ||
* | ||
* @return array | ||
*/ | ||
|
@@ -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); | ||
|
||
|
@@ -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); | ||
|
||
|
@@ -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)) { | ||
|
@@ -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 | ||
|
@@ -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); | ||
|
@@ -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 | ||
|
@@ -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 = []; | ||
|
@@ -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 | ||
|
@@ -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; | ||
|
@@ -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 | ||
|
@@ -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 = []; | ||
|
@@ -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 | ||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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 = []; | ||
|
@@ -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 | ||
|
@@ -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; | ||
|
@@ -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. | ||
* | ||
|
@@ -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); | ||
|
@@ -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 = []; | ||
|
@@ -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; | ||
|
@@ -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++) { | ||
//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; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.