Skip to content

Commit

Permalink
feat: Complex return types for ethereum:call
Browse files Browse the repository at this point in the history
- pass ABI output types to ethers when as="abi" is specified
- use ts:data ref path to access nested data from other attributes
  • Loading branch information
micwallace committed Dec 11, 2023
1 parent df143e3 commit d30fb05
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 11 deletions.
1 change: 1 addition & 0 deletions javascript/engine-js/package-lock.json

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

1 change: 1 addition & 0 deletions javascript/engine-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"ethers-decode-error": "^1.0.0",
"ipfs-only-hash": "^4.0.0",
"jsdom": "^22.1.0",
"lodash": "^4.17.21",
"pako": "^2.1.0",
"webcrypto-liner": "^1.4.2",
"xmldsigjs": "^2.5.1"
Expand Down
49 changes: 45 additions & 4 deletions javascript/engine-js/src/tokenScript/Attribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {EthUtils} from "../ethereum/EthUtils";
import {FilterQuery} from "./data/event/FilterQuery";
import {AbstractDependencyBranch} from "./data/AbstractDependencyBranch";
import {Label} from "./Label";
import LodashGet from "lodash/get";

interface TokenAttributeValue {
[tokenId: string]: any
Expand Down Expand Up @@ -187,7 +188,8 @@ export class Attribute {

console.log("Resolving attribute: " + this.getName() + " for token context " + tokenContext?.selectedTokenId);

const contract = this.tokenScript.getContractByName(origin.getAttribute("contract"));
const contractName = origin.getAttribute("contract")
const contract = this.tokenScript.getContractByName(contractName);
const wallet = await this.tokenScript.getEngine().getWalletAdapter();
const chain = tokenContext?.chainId ?? await wallet.getChain();
const contractAddr = contract.getAddressByChain(chain, true);
Expand All @@ -199,6 +201,20 @@ export class Attribute {
const func = origin.getAttribute("function");

let outputType = EthUtils.tokenScriptOutputToEthers(this.asType);
let outputTypes;

if (outputType === "abi"){

const abi = contract.getAbi("function", func);

if (!abi.length){
throw new Error("'as' XML attribute specifies abi but the abi for " + contractName + ":" + func + " is not defined");
}

outputTypes = abi[0].outputs;
} else {
outputTypes = [outputType];
}

const args = new Arguments(this.tokenScript, origin, this.localAttrContext).getArguments();

Expand All @@ -208,7 +224,7 @@ export class Attribute {
ethParams.push(await args[i].getEthersArgument(tokenContext, i.toString()))
}

res = await wallet.call(contractAddr.chain, contractAddr.address, func, ethParams, [outputType]);
res = await wallet.call(contractAddr.chain, contractAddr.address, func, ethParams, outputTypes);

console.log("Call result: ", res);

Expand Down Expand Up @@ -251,7 +267,7 @@ export class Attribute {
console.log("Event result: ", res);
}

if (res instanceof Object) {
if (res instanceof Object && res._isBigNumber) {
resultValue = BigInt(res);
} else {
resultValue = res;
Expand All @@ -267,7 +283,32 @@ export class Attribute {
throw new Error("Attribute ts:data origin does not support more than one element");
}

resultValue = data[0].textContent;
if (origin.hasAttribute("ref")){

// TODO: prevent single attribute references (i.e. must be path)? Maybe not if this code gets shared for transaction argument resolution

// Check for path reference and extract attribute name
const ref = origin.getAttribute("ref");
const firstElem = ref.match(/\.|\[/)?.[0];
const attrName = firstElem ? ref.split(firstElem)[0] : ref;
let path = firstElem ? ref.substring(ref.indexOf(firstElem)) : "";

if (path.charAt(0) === ".")
path = path.substring(1);

//console.log("Resolving attribute data reference, attribute:", attrName, " path:", path);

const attribute = this.tokenScript.getAttributes().getAttribute(attrName);

const value = await attribute.getValue(true, false, false, tokenContext);

if (path) {
resultValue = LodashGet(value, path);
}

} else {
resultValue = data[0].textContent;
}

break;

Expand Down
1 change: 0 additions & 1 deletion javascript/engine-js/src/tokenScript/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export class Contract {
if (abiXml.length){
try {
this.abi = JSON.parse(abiXml[0].innerHTML);
console.log(this.abi);
} catch (e){
console.warn("Failed to parse contract ABI", e);
}
Expand Down
10 changes: 5 additions & 5 deletions javascript/engine-js/src/wallet/EthersAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class EthersAdapter implements IWalletAdapter {
return await ethersProvider.getSigner().signMessage(data);
}

async call(chain: number, contractAddr: string, method: string, args: any[], outputTypes: string[], errorAbi: any[] = []){
async call(chain: number, contractAddr: string, method: string, args: any[], outputTypes: string[]|any[], errorAbi: any[] = []){

console.log("Call ethereum method. chain " + chain + "; contract " + contractAddr + "; method " + method + ";");
//console.log(args);
Expand Down Expand Up @@ -100,22 +100,22 @@ export class EthersAdapter implements IWalletAdapter {
return tx;
}

private async getEthersContractInstance(chain: number, contractAddr: string, method: string, args: any[], outputTypes: string[], stateMutability: string, errorAbi: any[] = []){
private async getEthersContractInstance(chain: number, contractAddr: string, method: string, args: any[], outputTypes: string[]|any[], stateMutability: string, errorAbi: any[] = []){

const abiData = {
name: method,
inputs: args,
outputs: outputTypes.map((value, index) => {
outputs: typeof outputTypes[0] === "string" ? outputTypes.map((value, index) => {

// Converted value
const convertedType = value === "uint" ? "uint256" : value;

return {
name: index.toString(),
name: value.name ?? index.toString(),
type: convertedType,
internalType: convertedType
}
}),
}) : outputTypes,
stateMutability: stateMutability,
type: "function"
};
Expand Down
2 changes: 1 addition & 1 deletion javascript/engine-js/src/wallet/IWalletAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface IWalletAdapter {
contractAddr: string,
method: string,
args: any[],
outputTypes: string[],
outputTypes: any[]|string[],
errorAbi?: any[]
): Promise<any>;
getEvents(
Expand Down
1 change: 1 addition & 0 deletions javascript/tokenscript-viewer/package-lock.json

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

1 change: 1 addition & 0 deletions javascript/tokenscript-viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"ethers-decode-error": "^1.0.0",
"ipfs-only-hash": "^4.0.0",
"jsdom": "^22.1.0",
"lodash": "^4.17.21",
"pako": "^2.1.0",
"typescript-memoize": "^1.1.1",
"webcrypto-liner": "^1.4.2",
Expand Down

0 comments on commit d30fb05

Please sign in to comment.