How to return an Array in an struct? - struct

I wrote a contract that keeps an array of the "wRequest", which is the type of a constructer I wrote. But when I add something to the list and try to return it, I can only get the arguments that are not arrays.
what seems to be problem?
Contract :
pragma solidity ^0.8.0;
contract s {
WRequest[] public WRequestList;
struct WRequest {
address receiver;
address[] tokenAddresss;
uint256[] amounts;
uint approved;
}
function submitWithdrawRequest(address receiver, address[] memory tokenAddresss, uint256[] memory amounts) public {
WRequest memory request = WRequest(receiver, tokenAddresss, amounts, 0);
WRequestList.push(request);
}
}
deploy.js
const hre = require("hardhat");
async function main() {
const Lock = await hre.ethers.getContractFactory("s");
const lock = await Lock.deploy();
const [owner, account1 ,account2] = await ethers.getSigners();
await lock.deployed();
console.log("deployed")
await lock.submitWithdrawRequest(owner.address, [account1.address], [7])
const list = await lock.WRequestList(0);
console.log(list);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Result
[
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
BigNumber { value: "0" },
receiver: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
approved: BigNumber { value: "0" }
]
It only includes the receiver address and approved, and not the array of tokenAddresss and amounts.

You can write a function for this
function getWRequest(uint _index) public view returns(WRequest memory) {
return WRequestList[_index];
}

Related

How to access parent query arguments in GraphQL and Nestjs execution context

Let's say we have a bookshop and an author entity, to show the author their earnings stat, we want to check if the authenticated user is indeed the author themselves. So we have:
#UseGuards(GqlAuthGuard)
#ResolveField(() => [Eearning], { name: 'earnings' })
async getEarnings(
#Parent() author: Author,
#GqlUser() user: User,
) {
if (user.id !== author.id)
throw new UnauthorizedException(
'Each author can only view their own data',
);
// rest of the function implementation
}
We could query this:
query {
author(id: "2bd79-6d7f-76a332b06b") {
earnings {
sells
}
}
}
Now imagine we want to use a custom Guard instead of that if statement. Something like below:
#Injectable()
export class AutherGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
// const artistId = ?
}
}
How can I access the id argument given to the author query when AutherGuard is used for the getEarnings handler?
Not sure how documented is that but the parent object can be accessed through the getRoot method:
const gqlContext = GqlExecutionContext.create(context);
const root = gqlContext.getRoot();
const authorId = root.id;
In fact, we have a helper function that we use like this:
export function getArgs(context: ExecutionContext): any {
if (context.getType<GqlContextType>() === "graphql") {
const gqlContext = GqlExecutionContext.create(context);
return { ...gqlContext.getArgs(), $parent: gqlContext.getRoot() };
} else if (context.getType() === "http") {
return context.switchToHttp().getRequest().params;
}
}
...
const args = getArgs(context);
const authorId = _.get(args, "$parent.id");

Smart Contract could transfer ether to an address, but the balance of that address does not update

I am trying to get my smart contract to transfer all its balance to another address. The transfer line doesn't throw any errors but the balance of contract does not change afterwards.
I am using web3js with ganache to test this function:
My contract:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.12;
contract Lottery {
address payable public manager;
address payable[] public players;
constructor() {
manager = payable(msg.sender);
}
function enterLottery() public payable {
require(msg.value >= .01 ether);
players.push(payable(msg.sender));
}
function getPlayers() public view returns (address payable[] memory) {
return players;
}
function random() public view returns (uint256) {
return
uint256(
keccak256(
abi.encodePacked(block.difficulty, block.timestamp, players)
)
);
}
function pickWinner() public { // this transfer contract balance to the account
uint256 index = random() % players.length;
players[index].transfer(address(this).balance);
}
}
My test case:
beforeEach(async () => {
accounts = await web3.eth.getAccounts();
contract = await new web3.eth.Contract(abi)
.deploy({ data: evm.bytecode.object })
.send({ from: accounts[0], gas: "1000000" })
})
describe("Lottery", () => {
it("Contract has an address? ", () => {
assert.ok(contract.options.address)
})
it("Prize pool can receive ether", async () => {
await contract.methods.enterLottery().send({ from: accounts[1], gas: "1000000", value: "10000000000000000" });
const contractBalance = await web3.eth.getBalance(contract.options.address)
const hasContractReceivedEntry = contractBalance === "10000000000000000";
assert.equal(hasContractReceivedEntry, true)
})
it("Winner can receive the prize pool", async () => {
await contract.methods.enterLottery().send({ from: accounts[1], gas: "1000000", value: "10000000000000000" });
await contract.methods.pickWinner().call();
const contractBalance = await web3.eth.getBalance(contract.options.address)
console.log(contractBalance) // the contract balance should be 0 after the pickWinner call, but it is still 10000000000000000 wei the enterLottery function gave
})
})
Edit: It is confirmed that the smart contract can run enterLottery and random() as intended
await contract.methods.pickWinner().call();
On this line, you're invoking a read-only call that doesn't update the contract state. You need to send a transaction using the .send() function - just like on the previous line.

How to pass a child Interface to a parent class?

I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.

ZeroMQ retransmitting pub-sub model

I'm trying to use the ZeroMQ javascript bindings to implement a re-transmitting pub-sub backbone.
Something to the effect of:
inbound modules announce themselves to the backbone, the backbone subscribes to them and re-transmits on its own publisher socket
outbound modules subscribe to backbone
I'm running into an issue with the pub sockets required to be used from a single thread though.
My current code, was something like this:
async function listen(name: string, sub: zmq.Subscriber, pub: zmq.Publisher) {
let id = 0;
for await (const [topic, msg] of sub) {
console.log(`BACKBONE | ${name} | received a message id: ${++id} related to: ${topic.toString()} containing message: ${msg.toString()}`);
await pub.send([topic, msg]);
}
}
Which gets instantiated for each of the inbound modules, but then of course they clash on pub.send.
I wrote a queue to serialize the socket access, which resolved the problem.
import * as zmq from "zeromq"
export class PublisherQueue {
private queue: Buffer[][] = []
constructor(private publisher: zmq.Publisher) { }
private static toBuffer(value: Buffer | string): Buffer {
if (value instanceof Buffer) {
return value as Buffer;
} else {
return Buffer.from(value as string);
}
}
send(topic: Buffer | string, msg: Buffer | string) {
this.queue.push([PublisherQueue.toBuffer(topic), PublisherQueue.toBuffer(msg)]);
}
async run() {
while (true) {
if (this.queue.length > 0) {
let msg = this.queue.shift();
if (msg !== undefined) {
await this.publisher.send(msg);
}
} else {
await new Promise(resolve => setTimeout(resolve, 50));
}
}
}
}

getting undefined from async await in typescript node

Use case:
I am trying to insert a record inside the amazon QLDB using Node and typescript.
I am able to insert the record/document successfully and it returns me documentID in return.
there are 2 controllers: EntityController and CommonController
-EntityController extends CommonController
-EntityController has the code for getting req object converting it into the model object and the calling insert() function that has been extended from the CommonController.
problem
I am trying to propagate that documentID to all the way to my API call, but somehow I am getting undefined in the EntityController.
whereas I am able to print the documentID in CommonController.
I am not sure why I am getting undefined when I am clearly returning a value.
const CommonController = require("../template/controller");
import { Request, Response } from 'express';
const tableName:string = "entities";
const EntityModel = require("./model")
class EntityController extends CommonController {
async insertEntitiy(req:Request,res:Response) {
async insertEntitiy(req:any,res:any) {
console.log(req);
console.log("===========");
console.log(req.body);
let entity = new EntityModel();
entity.balance = req.body.balance;
entity.firstName = req.body.firstName;
entity.lastName = req.body.lastName;
entity.email = req.body.email;
try {
let documentIds = await this.insert(tableName,entity);
console.log("--------- inside insertEntity fiunction()---------");
console.log(documentIds);
console.log("------------------");
res.status(200).send(documentIds[0]);
} catch (error) {
console.error(`error in creating Entity: ${error}`);
res.status(500).send({ errMsg: `error in creating Entity: ${error}` });
}
}
}
module.exports = new EntityController();
import { createQldbWriter, QldbSession, QldbWriter, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
import { Reader } from "ion-js";
import { error, log } from "../qldb/LogUtil";
import { getFieldValue, writeValueAsIon } from "../qldb/Util";
import { closeQldbSession, createQldbSession } from "../qldb/ConnectToLedger";
module.exports = class Conroller {
async insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result:Array<string>;
try {
session = await createQldbSession();
await session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return (Promise.resolve(result));
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
/**
* Insert the given list of documents into a table in a single transaction.
* #param txn The {#linkcode TransactionExecutor} for lambda execute.
* #param tableName Name of the table to insert documents into.
* #param documents List of documents to insert.
* #returns Promise which fulfills with a {#linkcode Result} object.
*/
async insertDocument(
txn: TransactionExecutor,
tableName: string,
documents: object
): Promise<Array<string>> {
const statement: string = `INSERT INTO ${tableName} ?`;
const documentsWriter: QldbWriter = createQldbWriter();
let documentIds: Array<string> = [];
writeValueAsIon(documents, documentsWriter);
let result: Result = await txn.executeInline(statement, [documentsWriter]);
const listOfDocumentIds: Reader[] = result.getResultList();
listOfDocumentIds.forEach((reader: Reader, i: number) => {
documentIds.push(getFieldValue(reader, ["documentId"]));
});
console.log("---------documentIds---------");
console.log(documentIds);
console.log("------------------");
return (documentIds);
}
}
ouptut :
---------documentIds---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
---------result inside insert fiunction()---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
--------- inside insertEntity fiunction()---------
undefined
As #daniel-w-strimpel pointed out in the comments, your insert method returns only in the catch part.
Try this:
insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result: Array<string>;
try {
session = await createQldbSession();
return session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return result;
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
...
In return session.executeLambda you return the Promise.
In return result; you return the actual value.
More on promises here: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Resources