Skip to content

Commit

Permalink
Added: events ERC20.OnTransfer() and ERC20.OnApproval()
Browse files Browse the repository at this point in the history
  • Loading branch information
svanas committed Apr 12, 2019
1 parent aef8ada commit 96017c5
Show file tree
Hide file tree
Showing 9 changed files with 434 additions and 40 deletions.
83 changes: 80 additions & 3 deletions web3.eth.erc20.pas
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,42 @@ interface
uses
// Delphi
System.SysUtils,
System.Threading,
// web3
web3,
web3.eth,
web3.eth.logs,
web3.eth.types,
web3.types;

type
TERC20 = class;

TOnTransfer = reference to procedure(
Sender: TERC20;
From : TAddress;
&To : TAddress;
Value : UInt64);
TOnApproval = reference to procedure(
Sender : TERC20;
Owner : TAddress;
Spender: TAddress;
Value : UInt64);

TERC20 = class
private
FClient : TWeb3;
FContract: TAddress;
strict private
FTask : ITask;
FClient : TWeb3;
FContract : TAddress;
FOnTransfer: TOnTransfer;
FOnApproval: TOnApproval;
procedure SetOnTransfer(Value: TOnTransfer);
procedure SetOnApproval(Value: TOnApproval);
protected
procedure WatchOrStop; virtual;
public
constructor Create(aClient: TWeb3; aContract: TAddress); virtual;
destructor Destroy; override;

//------- read contract ----------------------------------------------------
procedure Name (callback: TASyncString);
Expand All @@ -54,6 +77,10 @@ TERC20 = class

property Client : TWeb3 read FClient;
property Contract: TAddress read FContract;

//------- events -----------------------------------------------------------
property OnTransfer: TOnTransfer read FOnTransfer write SetOnTransfer;
property OnApproval: TOnApproval read FOnApproval write SetOnApproval;
end;

implementation
Expand All @@ -63,8 +90,58 @@ implementation
constructor TERC20.Create(aClient: TWeb3; aContract: TAddress);
begin
inherited Create;

FClient := aClient;
FContract := aContract;

FTask := web3.eth.logs.get(aClient, aContract,
procedure(log: TLog)
begin
if Assigned(FOnTransfer) then
if log.isEvent('Transfer(address,address,uint256)') then
FOnTransfer(Self,
TAddress.New(log.Topic[1]),
TAddress.New(log.Topic[2]),
toInt(log.Data[0]));
if Assigned(FOnApproval) then
if log.isEvent('Approval(address,address,uint256)') then
FOnApproval(Self,
TAddress.New(log.Topic[1]),
TAddress.New(log.Topic[2]),
toInt(log.Data[0]));
end);
end;

destructor TERC20.Destroy;
begin
if FTask.Status = TTaskStatus.Running then
FTask.Cancel;
inherited Destroy;
end;

procedure TERC20.SetOnTransfer(Value: TOnTransfer);
begin
FOnTransfer := Value;
WatchOrStop;
end;

procedure TERC20.SetOnApproval(Value: TOnApproval);
begin
FOnApproval := Value;
WatchOrStop;
end;

procedure TERC20.WatchOrStop;
begin
if Assigned(FOnTransfer)
or Assigned(FOnApproval) then
begin
if FTask.Status <> TTaskStatus.Running then
FTask.Start;
EXIT;
end;
if FTask.Status = TTaskStatus.Running then
FTask.Cancel;
end;

procedure TERC20.Name(callback: TASyncString);
Expand Down
2 changes: 1 addition & 1 deletion web3.eth.gas.pas
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ implementation

procedure getGasPrice(client: TWeb3; callback: TASyncQuantity);
begin
web3.json.rpc.Send(client.URL, 'eth_gasPrice', [], procedure(resp: TJsonObject; err: Exception)
web3.json.rpc.send(client.URL, 'eth_gasPrice', [], procedure(resp: TJsonObject; err: Exception)
begin
if Assigned(err) then
callback(0, err)
Expand Down
224 changes: 224 additions & 0 deletions web3.eth.logs.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
{******************************************************************************}
{ }
{ Delphereum }
{ }
{ Copyright(c) 2019 Stefan van As <svanas@runbox.com> }
{ Github Repository <https://github.com/svanas/delphereum> }
{ }
{ Distributed under Creative Commons NonCommercial (aka CC BY-NC) license. }
{ }
{******************************************************************************}

unit web3.eth.logs;

{$I web3.inc}

interface

uses
// Delphi
System.JSON,
System.Threading,
// Velthuis' BigNumbers
Velthuis.BigIntegers,
// web3
web3,
web3.eth.types;

type
TLog = record
strict private
FBlockNumber: BigInteger;
FTopics : TTopics;
FData : TTuple;
function GetTopic(idx: Integer): TArg;
public
procedure Load(tx: TJsonValue);
function isEvent(const name: string): Boolean;
property BlockNumber: BigInteger read FBlockNumber;
property Topic[idx: Integer]: TArg read GetTopic;
property Data: TTuple read FData;
end;

type
PLog = ^TLog;

type
TASyncLog = reference to procedure(log: TLog);

function get(client: TWeb3; address: TAddress; callback: TASyncLog): ITask;

implementation

uses
// Delphi
System.Classes,
System.SysUtils,
// web3
web3.eth,
web3.json,
web3.json.rpc,
web3.utils;

{ TLog }

function TLog.GetTopic(idx: Integer): TArg;
begin
Result := FTopics[idx];
end;

procedure TLog.Load(tx: TJsonValue);
var
tpcs: TJsonArray;
tpc : Integer;
buf : TBytes;
arg : TArg;
last: PArg;
begin
FBlockNumber := web3.json.GetPropAsStr(tx, 'blockNumber');
// load the "topics"
tpcs := web3.json.GetPropAsArr(tx, 'topics');
if Assigned(tpcs) then
for tpc := 0 to Pred(tpcs.Count) do
begin
buf := web3.utils.fromHex(tpcs.Items[tpc].Value);
if Length(buf) >= SizeOf(TArg) then
begin
Move(buf[0], arg[0], SizeOf(TArg));
FTopics[tpc] := arg;
end;
end;
// load the "data"
buf := web3.utils.fromHex(web3.json.GetPropAsStr(tx, 'data'));
while Length(buf) >= SizeOf(TArg) do
begin
last := Data.Add;
Move(buf[0], last[0], SizeOf(TArg));
Delete(buf, 0, SizeOf(TArg));
end;
end;

function TLog.isEvent(const name: string): Boolean;
var
buf: TBytes;
arg: TArg;
begin
buf := web3.utils.sha3(web3.utils.toHex(name));
Move(buf[0], arg[0], SizeOf(TArg));
Result := CompareMem(@FTopics[0], @arg, SizeOf(TArg));
end;

{ TLogs }

type
TLogs = TArray<TLog>;

{ TLogsHelper }

type
TLogsHelper = record helper for TLogs
function Add : PLog;
function Last: PLog;
end;

function TLogsHelper.Add: PLog;
begin
SetLength(Self, Length(Self) + 1);
Result := Last;
end;

function TLogsHelper.Last: PLog;
begin
Result := nil;
if Length(Self) > 0 then
Result := @Self[High(Self)];
end;

{ private functions }

function blockNumber(client: TWeb3): BigInteger;
var
obj: TJsonObject;
begin
obj := web3.json.rpc.send(client.URL, 'eth_blockNumber', []);
if Assigned(obj) then
try
Result := web3.json.GetPropAsStr(obj, 'result');
finally
obj.Free;
end;
end;

function getAsArr(client: TWeb3; fromBlock: BigInteger; address: TAddress): TJsonArray;
var
&in : TJsonObject;
&out: TJsonObject;
arr : TJsonArray;
begin
Result := nil;
&in := web3.json.unmarshal(Format(
'{"fromBlock": "%s", "toBlock": %s, "address": %s}', [
web3.utils.toHex(fromBlock),
web3.json.QuoteString(BLOCK_LATEST, '"'),
web3.json.QuoteString(string(address), '"')
]
));
try
&out := web3.json.rpc.send(client.URL, 'eth_getLogs', [&in]);
if Assigned(&out) then
try
arr := web3.json.GetPropAsArr(&out, 'result');
if Assigned(arr) then
Result := arr.Clone as TJsonArray;
finally
&out.Free;
end;
finally
&in.Free;
end;
end;

function getAsLog(client: TWeb3; fromBlock: BigInteger; address: TAddress): TLogs;
var
arr : TJsonArray;
itm : TJsonValue;
last: PLog;
begin
SetLength(Result, 0);
arr := getAsArr(client, fromBlock, address);
if Assigned(arr) then
try
for itm in arr do
begin
last := Result.Add;
last.Load(itm);
end;
finally
arr.Free;
end;
end;

{ public functions }

function get(client: TWeb3; address: TAddress; callback: TASyncLog): ITask;
begin
Result := TTask.Create(procedure
var
bn : BigInteger;
log : TLog;
logs: TLogs;
begin
bn := blockNumber(client);
while TTask.CurrentTask.Status <> TTaskStatus.Canceled do
begin
logs := web3.eth.logs.getAsLog(client, bn, address);
for log in logs do
begin
bn := BigInteger.Max(bn, log.BlockNumber.Succ);
callback(log);
end;
end;
end);
end;

end.
8 changes: 4 additions & 4 deletions web3.eth.pas
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ procedure getBalance(client: TWeb3; address: TAddress; callback: TASyncQuantity)

procedure getBalance(client: TWeb3; address: TAddress; const block: string; callback: TASyncQuantity);
begin
web3.json.rpc.Send(client.URL, 'eth_getBalance', [address, block], procedure(resp: TJsonObject; err: Exception)
web3.json.rpc.send(client.URL, 'eth_getBalance', [address, block], procedure(resp: TJsonObject; err: Exception)
begin
if Assigned(err) then
callback(0, err)
Expand All @@ -124,7 +124,7 @@ procedure getTransactionCount(client: TWeb3; address: TAddress; callback: TASync
// returns the number of transations *sent* from an address
procedure getTransactionCount(client: TWeb3; address: TAddress; const block: string; callback: TASyncQuantity);
begin
web3.json.rpc.Send(client.URL, 'eth_getTransactionCount', [address, block], procedure(resp: TJsonObject; err: Exception)
web3.json.rpc.send(client.URL, 'eth_getTransactionCount', [address, block], procedure(resp: TJsonObject; err: Exception)
begin
if Assigned(err) then
callback(0, err)
Expand Down Expand Up @@ -156,7 +156,7 @@ procedure call(client: TWeb3; from, &to: TAddress; const func, block: string; ar
// step #1: encode the function abi
abi := web3.eth.abi.encode(func, args);
// step #2: construct the transaction call object
obj := web3.json.Unmarshal(Format(
obj := web3.json.unmarshal(Format(
'{"from": %s, "to": %s, "data": %s}', [
web3.json.QuoteString(string(from), '"'),
web3.json.QuoteString(string(&to), '"'),
Expand All @@ -165,7 +165,7 @@ procedure call(client: TWeb3; from, &to: TAddress; const func, block: string; ar
));
try
// step #3: execute a message call (without creating a transaction on the blockchain)
web3.json.rpc.Send(client.URL, 'eth_call', [obj, block], procedure(resp: TJsonObject; err: Exception)
web3.json.rpc.send(client.URL, 'eth_call', [obj, block], procedure(resp: TJsonObject; err: Exception)
begin
if Assigned(err) then
callback('', err)
Expand Down
Loading

0 comments on commit 96017c5

Please sign in to comment.