Here is a function, given a WebAssembly smart contract as a file input, that will list out all exported contract functions of said contract.
private onLoadContract = async (event: any) => {
if (this.store.file === null) {
return;
}
const reader = new FileReader();
const bytes = await new Promise((resolve, reject) => {
reader.onerror = () => {
reader.abort();
reject(new DOMException("Failed to parse contract file."))
}
reader.onload = () => {
resolve(reader.result as any);
}
reader.readAsArrayBuffer(this.store.file as Blob);
});
// @ts-ignore
const contract = await WebAssembly.instantiate(bytes, {
env: {
_payload_len: () => 0, _payload: () => 0, _provide_result: () => 0, _send_transaction: () => 0
}
});
const contractPrefix = "_contract_";
let exportedFunctions = _.filter(Object.keys(contract.instance.exports), exp => exp.startsWith(contractPrefix));
exportedFunctions = _.map(exportedFunctions, exp => exp.substr(contractPrefix.length));
console.log(exportedFunctions);
}
Smart contract functions have parameters and return payloads as byte arrays. They are executed in the context of the transactions sender.
A UI component is needed to specify a list of parameters, and to parse the return payloads of a contracts execution. The return payload of a transaction is collected by locally executing the smart contract function on the client-side.
An option exists to also directly send the smart contract function call transaction as-is to the ledger via /transaction/send.
Example parameters and result for a function that sends some money to a recipient:
Function name: _contract_transfer
Function params: [(String) "recipient wallet address here", (unsigned int 64) 100]
Function return results: [(boolean which is an unsigned 8-bit int) true]
All data types are laid out to be little-endian.
The smart contract function transfer
is defined in Rust as:
fn transfer(&mut self, params: &mut Parameters) -> Option<Payload> {
let recipient: Vec<u8> = params.read();
let amount: u64 = params.read();
let mut result = Payload::new();
let sender_balance = match self.balances.get(¶ms.sender) {
Some(balance) => *balance,
None => 0
};
// Throw an error if the sender does not have enough balance.
if sender_balance < amount {
result.write(&false);
return Some(result);
}
let recipient_balance = match self.balances.get(&recipient) {
Some(balance) => *balance,
None => 0
};
// Modify balances.
self.balances.insert(params.sender.clone(), sender_balance - amount);
self.balances.insert(recipient, recipient_balance + amount);
// Return `true` to the sender that the transfer was successful.
result.write(&true);
return Some(result);
}
Strings are laid out to end with a termination character \0
:
impl Writeable for String {
fn write_to(&self, buffer: &mut Vec<u8>) {
for x in self.chars() {
x.write_to(buffer);
}
'\0'.write_to(buffer);
}
}
Byte arrays (Vec) are prefixed with their length:
impl<T: Writeable> Writeable for Vec<T> {
fn write_to(&self, buffer: &mut Vec<u8>) {
self.len().write_to(buffer);
for x in self {
x.write_to(buffer);
}
}
}
Booleans are 8-bit unsigned integers that are either 0 or 1.