Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pelacli_data/
test_consensus/
test_consensus_app/
multichain/
as/node_modules/
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
VERSION=v2.4.0
ASC=npx --prefix as asc
WASM_TESTDATA_DIR=wasmstrategy/testdata
WASM_TESTDATA=$(WASM_TESTDATA_DIR)/uniswap_strategy.wasm \
$(WASM_TESTDATA_DIR)/forbidden_import.wasm \
$(WASM_TESTDATA_DIR)/memory_import.wasm \
$(WASM_TESTDATA_DIR)/instruction_gas.wasm \
$(WASM_TESTDATA_DIR)/memory_grow.wasm

run:
go run cmd/main.go \
Expand Down Expand Up @@ -32,6 +39,29 @@ clean:
tidy:
go mod tidy

wasm-deps:
npm --prefix as ci

wasm-build: wasm-deps
npm --prefix as run build

wasm-testdata: wasm-deps $(WASM_TESTDATA)

$(WASM_TESTDATA_DIR)/uniswap_strategy.wasm: $(WASM_TESTDATA_DIR)/as/uniswap_strategy.ts
$(ASC) $< --runtime stub --optimize --shrinkLevel 2 --noAssert -o $@

$(WASM_TESTDATA_DIR)/forbidden_import.wasm: $(WASM_TESTDATA_DIR)/as/forbidden_import.ts
$(ASC) $< --runtime stub --optimize --shrinkLevel 2 --noAssert -o $@

$(WASM_TESTDATA_DIR)/memory_import.wasm: $(WASM_TESTDATA_DIR)/as/memory_import.ts
$(ASC) $< --runtime stub --optimize --shrinkLevel 2 --noAssert -o $@

$(WASM_TESTDATA_DIR)/instruction_gas.wasm: $(WASM_TESTDATA_DIR)/as/instruction_gas.ts
$(ASC) $< --runtime stub --optimize --shrinkLevel 2 --noAssert -o $@

$(WASM_TESTDATA_DIR)/memory_grow.wasm: $(WASM_TESTDATA_DIR)/as/memory_grow.ts
$(ASC) $< --runtime stub --optimize --shrinkLevel 2 --noAssert -o $@

tests:
go test -short -timeout 20m -failfast -shuffle=on -v ./... $(params)

Expand Down
6 changes: 4 additions & 2 deletions application/buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package application
import "github.com/ledgerwatch/erigon-lib/kv"

const (
AccountsBucket = "appaccounts" // token+account -> value
AccountsBucket = "appaccounts" // token+account -> value
StrategyStateBucket = "strategykv"
)

func Tables() kv.TableCfg {
return kv.TableCfg{
AccountsBucket: {},
AccountsBucket: {},
StrategyStateBucket: {},
}
}
22 changes: 22 additions & 0 deletions as/asconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"targets": {
"debug": {
"optimizeLevel": 0,
"shrinkLevel": 0,
"debug": true,
"outFile": "../build/strategy.debug.wasm"
},
"release": {
"optimizeLevel": 3,
"shrinkLevel": 2,
"noAssert": true,
"converge": false,
"outFile": "../build/strategy.wasm"
}
},
"options": {
"runtime": "stub",
"exportTable": true,
"exportMemory": true
}
}
53 changes: 53 additions & 0 deletions as/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions as/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "pelagos-strategy",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "asc src/strategy.ts --target release --config asconfig.json"
},
"devDependencies": {
"assemblyscript": "^0.27.0"
}
}
100 changes: 100 additions & 0 deletions as/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: MIT
// Pelagos AssemblyScript SDK shim exposing host functions provided by the Go runtime.

// EventKind enumerates the event payloads that the Go host exposes to strategies.
export enum EventKind {
Unknown = 0,
ERC20Transfer = 1,
}

// AddressId groups frequently used contracts so strategies can branch on a small enum.
export enum AddressId {
Unknown = 0,
UniswapV2Pair = 1,
UniswapV3Pool = 2,
}

// DbSlot identifiers backed by the runtime key/value store.
export const SLOT_LAST_UNI_TRANSFER_BLOCK: i32 = 1;

// LogLevel mirrors the numeric contract expected by env.log.
export enum LogLevel {
Debug = 10,
Info = 20,
Warn = 30,
Error = 40,
}

@external("env", "get_block_number")
declare function hostGetBlockNumber(): i64;

@external("env", "get_chain_id")
declare function hostGetChainId(): i64;

@external("env", "get_event_count")
declare function hostGetEventCount(): i32;

@external("env", "get_event_kind")
declare function hostGetEventKind(index: i32): i32;

@external("env", "get_event_address_id")
declare function hostGetEventAddressId(index: i32): i32;

@external("env", "db_get_u64")
declare function hostDbGet(slot: i32): i64;

@external("env", "db_put_u64")
declare function hostDbPut(slot: i32, value: i64): void;

@external("env", "log")
declare function hostLog(level: i32, ptr: usize, len: i32): void;

// getBlockNumber returns the current Pelagos block number.
export function getBlockNumber(): i64 {
return hostGetBlockNumber();
}

// getChainId returns the appchain identifier.
export function getChainId(): i64 {
return hostGetChainId();
}

// getEventCount returns how many events are available for this block.
export function getEventCount(): i32 {
return hostGetEventCount();
}

// getEventKind exposes the strongly typed EventKind for the event at index.
export function getEventKind(index: i32): EventKind {
return changetype<EventKind>(hostGetEventKind(index));
}

// getEventAddressId returns the AddressId classification for the event at index.
export function getEventAddressId(index: i32): AddressId {
return changetype<AddressId>(hostGetEventAddressId(index));
}

// dbGetU64 reads a 64-bit slot from the strategy KV store.
export function dbGetU64(slot: i32): i64 {
return hostDbGet(slot);
}

// dbPutU64 writes a 64-bit slot to the strategy KV store.
export function dbPutU64(slot: i32, value: i64): void {
hostDbPut(slot, value);
}

function writeLog(level: LogLevel, message: string): void {
const encoded = String.UTF8.encode(message);
hostLog(level, changetype<usize>(encoded), encoded.byteLength);
}

// logDebug emits a debugging log line.
export function logDebug(message: string): void {
writeLog(LogLevel.Debug, message);
}

// logInfo emits an informational log line.
export function logInfo(message: string): void {
writeLog(LogLevel.Info, message);
}
62 changes: 62 additions & 0 deletions as/src/strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
AddressId,
EventKind,
SLOT_LAST_UNI_TRANSFER_BLOCK,
dbGetU64,
dbPutU64,
getBlockNumber,
getEventAddressId,
getEventCount,
getEventKind,
logInfo,
} from "./sdk";

const BLOCK_STALE_THRESHOLD: i64 = 6000;

function hasUniswapTransfer(events: i32): bool {
for (let index = 0; index < events; index++) {
if (getEventKind(index) != EventKind.ERC20Transfer) {
continue;
}

const addressId = getEventAddressId(index);
if (
addressId == AddressId.UniswapV2Pair ||
addressId == AddressId.UniswapV3Pool
) {
return true;
}
}

return false;
}

// on_block is invoked by the Go runtime every Pelagos block.
export function on_block(): void {
const blockNumber = getBlockNumber();
const eventCount = getEventCount();

if (hasUniswapTransfer(eventCount)) {
dbPutU64(SLOT_LAST_UNI_TRANSFER_BLOCK, blockNumber);
logInfo(
"Uniswap ERC20 transfer detected at block " + blockNumber.toString()
);

return;
}

const lastSeen = dbGetU64(SLOT_LAST_UNI_TRANSFER_BLOCK);
if (lastSeen <= 0) {
return;
}

const delta = blockNumber - lastSeen;
if (delta > BLOCK_STALE_THRESHOLD) {
logInfo(
"No Uniswap transfers observed since block " +
lastSeen.toString() +
", delta=" +
delta.toString()
);
}
}
Binary file added build/strategy.wasm
Binary file not shown.
Loading
Loading