Skip to content
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

Contract wrapper #9

Merged
merged 12 commits into from
Aug 9, 2021
Merged

Contract wrapper #9

merged 12 commits into from
Aug 9, 2021

Conversation

claudiu725
Copy link
Contributor

Implements a contract wrapper which allows an user to interact with smart contracts, with the main focus being on simplicity.
The examples which this pull request enables can be found in: multiversx/mx-sdk-rs#268

  • Added ContractWrapper - a wrapper over a smart contract
    • it uses the ABI json to create methods for calling, querying, formatting
      • example call: await adder.gas(3_000_000).call.add(30);
      • example query: let sum = await adder.query.getSum();
      • format is used for packing transaction data into a FormattedCall object
        • it can be used to forward arguments to another call - as is the case for the multisig smart contract, where actions are "proposed"
        • allows checks during unit tests
        • allows inspection when debugging
    • may be used to deploy smart contracts
    • wrappers are loaded from a project folder
      • example
        • let adder = await erdSys.loadWrapper("contracts/examples/adder");
      • this loads the ABI, and if a deployment is requested, the WASM code as well
      • but the WASM is optional - useful when working with pre-deployed contracts
      • where more than one contract exists in the same folder, an additional argument may specify which one has to be loaded
        • let answer = await erdSys.loadWrapper("src/testdata", "answer");
    • calls with both EGLD and ESDT (FT, SFT, NFT) payments are supported
      • specified for a call via .value(MyToken(amount))
      • if the value being sent is an ESDT, the appropriate builtin function is called (ESDTTransfer or ESDTNFTTransfer)
    • added SendContext and ChainSendContext which help in keeping track of transaction properties
      • re-usable ones, which keep the old value, unless overwritten: provider, logger, sender, address, gas
      • one-time use: value
  • Added commonly used ABIs
    • ESDT system smart contract
    • builtin functions
    • send wrapper, for emulating EGLD and ESDT transfers without endpoint calls to as a method with an empty name
  • Updated existing ABIs with constructor information which is generated in the latest versions of elrond-wasm-rs
  • Added SystemWrapper class which encapsulates the builtin functions and ESDT system smart contract and provides a more intuitive API
  • Enhanced the Balance class - generalized it to accept other currencies as well
    • now it stores the token and nonce for which the amount is for
    • generalized it to handle any number of decimals
  • Added BalanceBuilder which can be used to create Balance instances for any EGLD or ESDT
    • denominated values can be specified by MyToken(10.0)
    • when receiving raw values from smart contracts, a non-denominated value can be built by using MyToken.raw(amount)
  • Added IProvider (ProxyProvider) endpoints
    • address/:address/esdt - getAddressEsdtList
    • address/:address/esdt/:tokenIdentifier - getAddressEsdt
    • address/:address/nft/:tokenIdentifier/nonce/:nonce - getAddressEsdt
  • Added NativeSerializer namespace which allow easier conversion from javascript native types to the typed value system of erdjs
    • it allows calling myMethod(1_000_000) instead of myMethod(new U32Value(1_000_000))
    • the argument is automatically cast to the appropriate type (deduced from the ABI json)
    • in case of unexpected input, useful error messages are returned through ArgumentErrorContext
  • Added several convenience functions which were useful in contract examples: print, printList, minutesToNonce, now, hours, minutes
  • Added utf8 string parsing
  • Updated test wallets (alice, bob, carol etc.)
    • Test wallets are no longer embedded in the typescript source code, but instead are loaded from their own files
    • Instead of 3 test wallets, now all 12 test wallets are included
  • Added npm run lint command
    • Added no-floating-promises check
  • Refactor provider selection
    • Added chooseProvider function which accepts one of local-testnet, elrond-testnet, elrond-devnet, elrond-mainnet
    • Removed getDevnetProvider, getTestnetProvider, getMainnetProvider because of confusing naming (getDevnetProvider was returning the proxy for http://localhost:7950)
  • Refactor result logic
    • De-duplicate code by extracting it into a common namespace Result
    • Rename ImmediateResult to TypedResult and re-use it for both immediate results and resulting calls
  • Removed several out-of-date comments

@claudiu725 claudiu725 changed the base branch from main to development June 28, 2021 14:27
Comment on lines 219 to 248
if (type instanceof U8Type) {
return new U8Value(number);
}
if (type instanceof I8Type) {
return new I8Value(number);
}
if (type instanceof U16Type) {
return new U16Value(number);
}
if (type instanceof I16Type) {
return new I16Value(number);
}
if (type instanceof U32Type) {
return new U32Value(number);
}
if (type instanceof I32Type) {
return new I32Value(number);
}
if (type instanceof U64Type) {
return new U64Value(number);
}
if (type instanceof I64Type) {
return new I64Value(number);
}
if (type instanceof BigUIntType) {
return new BigUIntValue(number);
}
if (type instanceof BigIntType) {
return new BigIntValue(number);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/esdtToken.ts Outdated
Comment on lines 5 to 7
FungibleESDT,
SemiFungibleESDT,
NonFungibleESDT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming these to Fungible, Semifungible, Nonfunginble, i.e. without the ESDT suffix and without capitalizing the word "fungible" when prefixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/esdtToken.ts Outdated
FungibleESDT,
SemiFungibleESDT,
NonFungibleESDT
}

export class ESDTToken {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, but consider renaming ESDTToken to TokenDefinition, or simply Token. It's true that this class is used for ESDT, but also EGLD, which is not an ESDT. Moreover, ESDT means "Elrond Standard Digital Token", so the "T" in ESDT already means "Token". ESDTToken is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True 😄
Renamed to Token

src/esdtToken.ts Outdated
Comment on lines 72 to 73
let [tokenName, tokenType, owner, totalMinted, totalBurned, ...propertiesBuffers] = results;
let properties = parseTokenProperties(propertiesBuffers);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting these two lines into a separate function which takes results and returns properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I end up unpacking and then re-packing the first arguments when I extract those lines into a separate function:

function parseTokenPropertiesFromResults(results: any[]): { tokenName: any, tokenType: any, owner: any, totalMinted: any, totalBurned: any, properties: Record<string, any> } {
    let [tokenName, tokenType, owner, totalMinted, totalBurned, ...propertiesBuffers] = results;
    let properties = parseTokenProperties(propertiesBuffers);
    return { tokenName, tokenType, owner, totalMinted, totalBurned, properties };
}

I think it's fine as it is right now.

src/esdtToken.ts Outdated
canChangeOwner: response.canChangeOwner,
canPause: response.canPause,
canFreeze: response.canFreeze,
canWipe: response.canWipe,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the properties canAddSpecialRoles, canTransferNftCreateRole, nftCreateStopped and wiped be also set here? Are they available in response?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment they aren't: https://testnet-api.elrond.com/tokens

src/esdtToken.ts Outdated
}

function parseValue(value: string): any {
switch (value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this switch, consider adding an if that checks if the value is strictly an alphanumeric string (without the decimal point), and if it is, create a BigNumber. Otherwise, go to the switch, but on the default branch, throw an exception (something like "unknown property").

This way, a malformed string will be prevented from reaching the BigNumber() constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is used to check the results of the query to getTokenProperties, which, for its properties has values of the form: NumDecimals-2, IsPaused-false.
Because they come from the ESDT system smart contract, I don't expect them to be malformatted.


function constructorFromJSON(json: any): EndpointDefinition | null {
// if the JSON "constructor" field is missing
if (json.constructor == Object.constructor) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safer to test for json.constructor.inputs and json.constructor.outputs. If either of them is missing, then there's no definition in the ABI for the contract constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* Returns whether two objects have the same value.
*/
equals(other: StringValue): boolean {
return this.value == other.value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For extra safety, use === here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. There's a linter check for this, but it's explicitly disabled at the moment. It should be re-enabled at some point.

let symbolsRegex = /(:|\{|\}|,|\s)/;
let tokens = jsoned.split(symbolsRegex).filter((token) => token);
jsoned = tokens.map((token) => (symbolsRegex.test(token) ? token : `"${token}"`)).join("");
let symbolsRegex = /(:|\{|\}|,|(?<!utf\-8)\s)/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean "a symbol is one of :{}, and space (\s), but up to and excluding utf-8"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The utf-8 string is a single type but because this code was splitting by space, I had to add an exception.
The (?<!utf\-8) is a negative look-ahead.
I added a comment for this in the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend moving these lines to a method called splitBySpaceExceptUTF8(), then adding a few small unit tests just for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't neatly split this into a separate function (since the symbolsRegex is used in 2 places, and bringing both lines into the function would bring the rest of the code in as well, because of the re-used tokens and jsoned ).
However, this function is well tested through the parse method. I added a test-case for the utf-8 string to the already existing tests in typeExpressionParser.spec.ts.

export type Method<ReturnType> = (...args: any[]) => ReturnType;
export type Methods<ReturnType> = Record<string, Method<ReturnType>>;

export function generateMethods<ThisType, ReturnType>(this_: ThisType, abi: SmartContractAbi, endpointHandler: EndpointHandler<ThisType, ReturnType>): Methods<ReturnType> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please format as one parameter per line, for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// A function may have one of the following formats:
// f(arg1, arg2, optional<arg3>, optional<arg4>) returns { min: 2, max: 4, variadic: false }
// f(arg1, variadic<bytes>) returns { min: 1, max: Infinity, variadic: true }
// f(arg1, arg2, optional<arg3>, arg4, optional<arg5>, variadic<bytes>) returns { min: 4, max: Infinity, variadic: true }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return { min: 2, max: Infinity, variadic: true }? Only the first 2 arguments are required, the rest are optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I corrected the comment.

alyn509
alyn509 previously approved these changes Jul 29, 2021
@@ -94,7 +96,7 @@ export class WalletProvider implements IDappProvider {
/**
* Fetches the login hook url and redirects the client to the wallet login.
*/
async login(options?: { callbackUrl?: string }): Promise<string> {
async login(options?: { callbackUrl?: string; token?: string }): Promise<string> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 141 will overwrite any value provided to this argument here. Is this intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was displayed due to more commits being made on the development branch and it's not something I wrote.
Basically, I had to do a merge from development first.

let symbolsRegex = /(:|\{|\}|,|\s)/;
let tokens = jsoned.split(symbolsRegex).filter((token) => token);
jsoned = tokens.map((token) => (symbolsRegex.test(token) ? token : `"${token}"`)).join("");
let symbolsRegex = /(:|\{|\}|,|(?<!utf\-8)\s)/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend moving these lines to a method called splitBySpaceExceptUTF8(), then adding a few small unit tests just for it.

Comment on lines 325 to 332
if (!this.isValidWalletSource(ev.origin)) {
return;
}

const { data } = ev;
if (data.type !== DAPP_MESSAGE_SIGN_TRANSACTION_URL) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be return reject(...); instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - these were changes from the development branch.

plainTransaction["value"] = transaction.getValue().toString();
plainTransaction["gasPrice"] = transaction.getGasPrice().valueOf();
plainTransaction["gasLimit"] = transaction.getGasLimit().valueOf();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chain ID and transaction spec version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - these were changes from the development branch.

@claudiu725 claudiu725 merged commit ade6d67 into development Aug 9, 2021
@claudiu725 claudiu725 deleted the contract-wrapper branch August 9, 2021 10:48
andreibancioiu added a commit that referenced this pull request Sep 25, 2024
Better handle transaction completion: proxy vs. API
danielailie pushed a commit that referenced this pull request Oct 2, 2024
danielailie pushed a commit that referenced this pull request Oct 2, 2024
fixed changelog, export encryptor/decryptor and prepared release
danielailie pushed a commit that referenced this pull request Oct 2, 2024
danielailie pushed a commit that referenced this pull request Oct 2, 2024
fixed changelog, export encryptor/decryptor and prepared release
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants