Skip to content

Commit

Permalink
Use new solana mechanism for return data
Browse files Browse the repository at this point in the history
Requires:
 - solana-labs/solana#19548
 - solana-labs/solana#19318

Signed-off-by: Sean Young <sean@mess.org>
  • Loading branch information
seanyoung committed Sep 18, 2021
1 parent 4016ef1 commit 7cc6e0a
Show file tree
Hide file tree
Showing 14 changed files with 449 additions and 254 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
needs: linux
services:
solana:
image: solanalabs/solana:v1.7.3
image: solanalabs/solana:edge
ports:
- 8899
- 8900
Expand Down
1 change: 0 additions & 1 deletion integration/solana/calls.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import expect from 'expect';
import { establishConnection } from './index';
import crypto from 'crypto';

describe('Deploy solang contract and test', () => {
it('external_call', async function () {
Expand Down
9 changes: 9 additions & 0 deletions integration/solana/errors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
contract errors {
function do_revert(bool yes) pure public returns (int) {
if (yes) {
revert("Do the revert thing");
} else {
return 3124445;
}
}
}
24 changes: 24 additions & 0 deletions integration/solana/errors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import expect from 'expect';
import { establishConnection } from './index';

describe('Deploy solang contract and test', () => {
it('errors', async function () {
this.timeout(50000);

let conn = await establishConnection();

let errors = await conn.loadProgram("bundle.so", "errors.abi");

// call the constructor
await errors.call_constructor(conn, 'errors', []);

let res = await errors.call_function(conn, "do_revert", [false]);

expect(res["0"]).toBe("3124445");

let revert_res = await errors.call_function_expect_revert(conn, "do_revert", [true]);

expect(revert_res).toBe("Do the revert thing");

});
});
102 changes: 92 additions & 10 deletions integration/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import fs from 'fs';
import { AbiItem } from 'web3-utils';
import { utils } from 'ethers';
import crypto from 'crypto';
import { encode } from 'querystring';
const Web3EthAbi = require('web3-eth-abi');

const default_url: string = "http://localhost:8899";
const return_data_prefix = 'Program return data: ';

export async function establishConnection(): Promise<TestConnection> {
let url = process.env.RPC_URL || default_url;
let connection = new Connection(url, 'recent');
let connection = new Connection(url, 'confirmed');
const version = await connection.getVersion();
console.log('Connection to cluster established:', url, version);

Expand Down Expand Up @@ -101,7 +103,7 @@ class TestConnection {
[this.payerAccount, account],
{
skipPreflight: false,
commitment: 'recent',
commitment: 'confirmed',
preflightCommitment: undefined,
},
);
Expand Down Expand Up @@ -190,7 +192,7 @@ class Program {
[test.payerAccount],
{
skipPreflight: false,
commitment: 'recent',
commitment: 'confirmed',
preflightCommitment: undefined,
},
);
Expand Down Expand Up @@ -221,7 +223,8 @@ class Program {
keys.push({ pubkey: PublicKey.default, isSigner: false, isWritable: false });

for (let i = 0; i < pubkeys.length; i++) {
keys.push({ pubkey: pubkeys[i], isSigner: false, isWritable: true });
// make each 2nd key writable (will be account storage for contract)
keys.push({ pubkey: pubkeys[i], isSigner: false, isWritable: (i & 1) == 1 });
}

const instruction = new TransactionInstruction({
Expand All @@ -232,24 +235,37 @@ class Program {

signers.unshift(test.payerAccount);

await sendAndConfirmTransaction(
let signature = await sendAndConfirmTransaction(
test.connection,
new Transaction().add(instruction),
signers,
{
skipPreflight: false,
commitment: 'recent',
commitment: 'confirmed',
preflightCommitment: undefined,
},
);

if (abi.outputs?.length) {
const accountInfo = await test.connection.getAccountInfo(this.contractStorageAccount.publicKey);
const parsedTx = await test.connection.getParsedConfirmedTransaction(
signature,
);

let encoded = Buffer.from([]);

let length = Number(accountInfo!.data.readUInt32LE(4));
let offset = Number(accountInfo!.data.readUInt32LE(8));
let seen = 0;

let encoded = accountInfo!.data.slice(offset, length + offset);
for (let message of parsedTx!.meta?.logMessages!) {
if (message.startsWith(return_data_prefix)) {
let [program_id, return_data] = message.slice(return_data_prefix.length).split(" ");
encoded = Buffer.from(return_data, 'base64')
seen += 1;
}
}

if (seen == 0) {
throw 'return data not set';
}

let returns = Web3EthAbi.decodeParameters(abi.outputs!, encoded.toString('hex'));

Expand All @@ -267,6 +283,72 @@ class Program {
}
}

async call_function_expect_revert(test: TestConnection, name: string, params: any[], pubkeys: PublicKey[] = [], seeds: any[] = [], signers: Keypair[] = []): Promise<string> {
let abi: AbiItem = JSON.parse(this.abi).find((e: AbiItem) => e.name == name);

const input: string = Web3EthAbi.encodeFunctionCall(abi, params);
const data = Buffer.concat([
this.contractStorageAccount.publicKey.toBuffer(),
test.payerAccount.publicKey.toBuffer(),
Buffer.from('00000000', 'hex'),
this.encode_seeds(seeds),
Buffer.from(input.replace('0x', ''), 'hex')
]);

let debug = 'calling function ' + name + ' [' + params + ']';

let keys = [];

seeds.forEach((seed) => {
keys.push({ pubkey: seed.address, isSigner: false, isWritable: true });
});

keys.push({ pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true });
keys.push({ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false });
keys.push({ pubkey: PublicKey.default, isSigner: false, isWritable: false });

for (let i = 0; i < pubkeys.length; i++) {
// make each 2nd key writable (will be account storage for contract)
keys.push({ pubkey: pubkeys[i], isSigner: false, isWritable: (i & 1) == 1 });
}

const instruction = new TransactionInstruction({
keys,
programId: this.programId,
data,
});

signers.unshift(test.payerAccount);

const { err, logs } = (await test.connection.simulateTransaction(new Transaction().add(instruction),
signers)).value;

if (!err) {
throw 'error is not falsy';
}

let encoded;
let seen = 0;

for (let message of logs!) {
if (message.startsWith(return_data_prefix)) {
let [program_id, return_data] = message.slice(return_data_prefix.length).split(" ");
encoded = Buffer.from(return_data, 'base64')
seen += 1;
}
}

if (seen == 0) {
throw 'return data not set';
}

if (encoded?.readUInt32BE(0) != 0x08c379a0) {
throw 'signature not correct';
}

return Web3EthAbi.decodeParameter('string', encoded.subarray(4).toString('hex'));
}

async contract_storage(test: TestConnection, upto: number): Promise<Buffer> {
const accountInfo = await test.connection.getAccountInfo(this.contractStorageAccount.publicKey);

Expand Down
18 changes: 0 additions & 18 deletions integration/solana/simple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,24 +407,6 @@ describe('Deploy solang contract and test', () => {
.toThrowError(new Error('failed to send transaction: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction'));
});

it('returndata too small', async function () {
this.timeout(50000);

let conn = await establishConnection();

// storage.sol needs 168 byes
let prog = await conn.loadProgram("bundle.so", "store.abi", 512);

await prog.call_constructor(conn, 'store', []);

await prog.call_function(conn, "set_foo1", []);

// get foo1
await expect(prog.call_function(conn, "get_both_foos", []))
.rejects
.toThrowError(new Error('failed to send transaction: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction'));
});

it('account storage too small dynamic alloc', async function () {
this.timeout(50000);

Expand Down
2 changes: 1 addition & 1 deletion src/emit/ewasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,7 +1527,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget {
}
}

fn return_data<'b>(&self, binary: &Binary<'b>) -> PointerValue<'b> {
fn return_data<'b>(&self, binary: &Binary<'b>, _function: FunctionValue) -> PointerValue<'b> {
let length = binary
.builder
.build_call(
Expand Down
2 changes: 1 addition & 1 deletion src/emit/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ impl<'a> TargetRuntime<'a> for GenericTarget {
}

/// Get return buffer for external call
fn return_data<'b>(&self, _binary: &Binary<'b>) -> PointerValue<'b> {
fn return_data<'b>(&self, _binary: &Binary<'b>, _function: FunctionValue) -> PointerValue<'b> {
panic!("generic cannot call other contracts");
}

Expand Down
76 changes: 15 additions & 61 deletions src/emit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ pub trait TargetRuntime<'a> {
) -> BasicValueEnum<'b>;

/// Return the return data from an external call (either revert error or return values)
fn return_data<'b>(&self, bin: &Binary<'b>) -> PointerValue<'b>;
fn return_data<'b>(&self, bin: &Binary<'b>, function: FunctionValue<'b>) -> PointerValue<'b>;

/// Return the value we received
fn value_transferred<'b>(&self, bin: &Binary<'b>, ns: &ast::Namespace) -> IntValue<'b>;
Expand Down Expand Up @@ -2537,7 +2537,7 @@ pub trait TargetRuntime<'a> {
)
.into()
}
Expression::ReturnData(_) => self.return_data(bin).into(),
Expression::ReturnData(_) => self.return_data(bin, function).into(),
Expression::StorageArrayLength { array, elem_ty, .. } => {
let slot = self
.expression(bin, array, vartab, function, ns)
Expand Down Expand Up @@ -6167,68 +6167,22 @@ impl<'a> Binary<'a> {
.unwrap()
.into_pointer_value()
} else {
// Get the type name of the struct we are point to
let struct_ty = vector
.into_pointer_value()
.get_type()
.get_element_type()
.into_struct_type();
let name = struct_ty.get_name().unwrap();

if name == CStr::from_bytes_with_nul(b"struct.SolAccountInfo\0").unwrap() {
// load the data pointer
let data = self
.builder
.build_load(
self.builder
.build_struct_gep(vector.into_pointer_value(), 3, "data")
.unwrap(),
"data",
)
.into_pointer_value();

// get the offset of the return data
let header_ptr = self.builder.build_pointer_cast(
data,
self.context.i32_type().ptr_type(AddressSpace::Generic),
"header_ptr",
);

let data_ptr = unsafe {
self.builder.build_gep(
header_ptr,
&[self.context.i64_type().const_int(2, false)],
"data_ptr",
)
};

let offset = self.builder.build_load(data_ptr, "offset").into_int_value();

let v = unsafe { self.builder.build_gep(data, &[offset], "data") };

self.builder.build_pointer_cast(
v,
self.context.i8_type().ptr_type(AddressSpace::Generic),
let data = unsafe {
self.builder.build_gep(
vector.into_pointer_value(),
&[
self.context.i32_type().const_zero(),
self.context.i32_type().const_int(2, false),
],
"data",
)
} else {
let data = unsafe {
self.builder.build_gep(
vector.into_pointer_value(),
&[
self.context.i32_type().const_zero(),
self.context.i32_type().const_int(2, false),
],
"data",
)
};
};

self.builder.build_pointer_cast(
data,
self.context.i8_type().ptr_type(AddressSpace::Generic),
"data",
)
}
self.builder.build_pointer_cast(
data,
self.context.i8_type().ptr_type(AddressSpace::Generic),
"data",
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/emit/sabre.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ impl<'a> TargetRuntime<'a> for SabreTarget {
}

/// Get return buffer for external call
fn return_data<'b>(&self, _binary: &Binary<'b>) -> PointerValue<'b> {
fn return_data<'b>(&self, _binary: &Binary<'b>, _function: FunctionValue) -> PointerValue<'b> {
panic!("Sabre cannot call other binarys");
}

Expand Down
Loading

0 comments on commit 7cc6e0a

Please sign in to comment.