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

Allow seeds to be passed into functions as string[] or string[][] #1547

Merged
merged 18 commits into from
Sep 28, 2023

Conversation

seanyoung
Copy link
Contributor

@seanyoung seanyoung commented Sep 21, 2023

This fixes this Solidity and similar constructs.

import "solana-library/spl_token.sol";

contract c {

    // Invoke the token program to mint tokens to a token account, using a PDA as the mint authority
    function mintTo(address mint, address account, address authority, uint64 amount, bytes[] seeds) internal {
        // Prepare instruction data
        bytes instructionData = new bytes(9);
        instructionData[0] = uint8(7); // MintTo instruction index
        instructionData.writeUint64LE(amount, 1); // Amount to mint

        // Prepare accounts required by instruction
        AccountMeta[3] metas = [
            AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
            AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
            AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
        ];

        // Invoke the token program with prepared accounts and instruction data
        SplToken.tokenProgramId.call{accounts: metas, seeds: seeds}(instructionData);
    }
}

Fixes #1433

@seanyoung seanyoung marked this pull request as ready for review September 21, 2023 13:23
@seanyoung seanyoung added the solana The Solana target label Sep 21, 2023
src/emit/expression.rs Outdated Show resolved Hide resolved
src/emit/instructions.rs Show resolved Hide resolved
src/sema/expression/literals.rs Outdated Show resolved Hide resolved
@LucasSte
Copy link
Contributor

import {AccountMeta} from 'solana';
            contract pda {
                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
                function test_bytes4(string[][] seeds) public {
                    bytes instr = new bytes(1);
                    instr[0] = 0x95;
                    AccountMeta[1] metas = [
                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
                    ];
                    tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
                }
            }

Is this expected?

error: conversion from string[][] to bytes slice slice not possible
   ┌─ /Users/lucasste/Documents/solang/examples/test.sol:11:48
   │
11 │                     tokenProgramId.call{seeds: seeds, accounts: metas}(instr);
   │

@LucasSte
Copy link
Contributor

            import {AccountMeta} from 'solana';
            contract pda {
                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
                function test(bytes[] dyn, address[] addr, bytes5[] b5) public {
                    bytes instr = new bytes(1);
                    instr[0] = 0x95;
                    AccountMeta[1] metas = [
                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
                    ];
                    bytes3 foo = "foo";
                    string foo2 = "123456";

                    tokenProgramId.call{seeds: [ dyn, addr, b5, [foo, foo2] ], accounts: metas}(instr);
                }
            }

Is this expected (part 2)?

error: conversion from string to bytes not possible
   ┌─ /Users/lucasste/Documents/solang/examples/test.sol:15:71
   │
15 │                     tokenProgramId.call{seeds: [ dyn, addr, b5, [foo, foo2] ], accounts: metas}(instr);
   │

@LucasSte
Copy link
Contributor

Strings should be allowed, shouldn't they?

            import {AccountMeta} from 'solana';
            contract pda {
                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
                function test(bytes[] dyn, string[] addr, bytes5[] b5) public {
                    bytes instr = new bytes(1);
                    instr[0] = 0x95;
                    AccountMeta[1] metas = [
                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
                    ];
                    bytes3 foo = "foo";
                    bytes6 foo2 = "123456";

                    tokenProgramId.call{seeds: [ dyn, addr, b5, [foo, foo2] ], accounts: metas}(instr);
                }
            }
error: type string found where array bytes slice expected
   ┌─ /Users/lucasste/Documents/solang/examples/test.sol:14:55
   │
14 │                     tokenProgramId.call{seeds: [ dyn, addr, b5, [foo, foo2] ], accounts: metas}(instr);
   │

@seanyoung
Copy link
Contributor Author

Type string is now allowed as a seed

@seanyoung seanyoung requested a review from LucasSte September 24, 2023 09:24
Comment on lines +2250 to +2259
let dest = call!("__malloc", &[i32_const!(bytes_length).into()])
.try_as_basic_value()
.left()
.unwrap()
.into_pointer_value();
Copy link
Contributor

Choose a reason for hiding this comment

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

You've already allocated memory here to save the bytes value. It is necessary to allocate again?

You can swap endianness in place and return a pointer to the stack allocated memory.

[1, 2, 3, 4, 5]
 ^           ^
 |-----------| ==> swap(1, 5)

[5, 2, 3, 4, 1]
    ^     ^
    |-----|   ==>  swap(2, 4)

[5, 4, 3, 2, 1]

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 other allocation is on the stack and that is a single allocation. This malloc allocation is per-slice.

Copy link
Contributor

@LucasSte LucasSte Sep 26, 2023

Choose a reason for hiding this comment

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

You do not need to allocate space in the stack and in the heap. You can allocate the memory in one place (heap or stack), store the integer there, change the endianness in place and return the pointer to that memory region.

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 might be something for another PR. We do all our endian-ness swaps like this.

In fact, I think bytesN should be represented by a byte array and then all these swaps would go away.

src/emit/polkadot/mod.rs Outdated Show resolved Hide resolved
@LucasSte
Copy link
Contributor

import {AccountMeta} from 'solana';
            contract pda {
                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
                function test(bytes[] dyn, address[] addr, bytes5[] b5, string f) public {
                    bytes instr = new bytes(1);
                    instr[0] = 0x95;
                    AccountMeta[1] metas = [
                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
                    ];
                    bytes3 foo = "foo";
                    tokenProgramId.call{seeds: [ dyn, addr, b5, [f] ], accounts: metas}(instr);
                }
            }

Is this correct?

thread 'main' panicked at 'internal error: entered unreachable code', src/emit/expression.rs:2123:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@LucasSte
Copy link
Contributor

I also tested the following example, but I am not confident it is going to be a common construct, so perhaps we should not worry about it.

import {AccountMeta} from 'solana';
            contract pda {
                address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
                address constant SYSVAR_RENT_PUBKEY = address"SysvarRent111111111111111111111111111111111";
                function test_bytes(bytes[][] seeds) public {
                    bytes instr = new bytes(1);
                    instr[0] = 0x95;
                    AccountMeta[1] metas = [
                        AccountMeta({pubkey: SYSVAR_RENT_PUBKEY, is_writable: false, is_signer: false})
                    ];
                    tokenProgramId.call{seeds: [seeds[0], seeds[1]], accounts: metas}(instr);
                }
            }
error: type bytes[] found where array expected
   ┌─ /Users/lucasste/Documents/solang/examples/test.sol:13:49
   │
13 │                     tokenProgramId.call{seeds: [seeds[0], seeds[1]], accounts: metas}(instr);
   │                                                 ^^^^^^^^

error: type bytes[] found where array expected
   ┌─ /Users/lucasste/Documents/solang/examples/test.sol:13:59
   │
13 │                     tokenProgramId.call{seeds: [seeds[0], seeds[1]], accounts: metas}(instr);
   │                                                           ^^^^^^^^

src/emit/binary.rs Outdated Show resolved Hide resolved
src/emit/expression.rs Outdated Show resolved Hide resolved
@seanyoung seanyoung force-pushed the seeds branch 2 times, most recently from c5e81da to 2958604 Compare September 27, 2023 08:46
@seanyoung
Copy link
Contributor Author

@LucasSte I've added the two cases you found as a test cases

Copy link
Contributor

@xermicus xermicus left a comment

Choose a reason for hiding this comment

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

LGTM overall, just some cosmetics

@@ -2120,3 +2122,227 @@ fn runtime_cast<'a>(
_ => unreachable!(),
}
}

/// Emit expression into a slice
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Superfluous. A more useful comment could for example say when this is needed or explain the return type (which I'd guess is a pointer to the slice and the length of the slice)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, that comment is terrible.

src/emit/expression.rs Show resolved Hide resolved
src/emit/expression.rs Show resolved Hide resolved
src/emit/expression.rs Show resolved Hide resolved
src/emit/expression.rs Show resolved Hide resolved
@seanyoung seanyoung requested a review from xermicus September 27, 2023 13:21
Copy link
Contributor

@xermicus xermicus left a comment

Choose a reason for hiding this comment

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

As discussed in the standup, it would be great to have those SAFETY comments, explaining our thoughts around why all those arguments to the LLVM GEP builder are correct. But it's not only in this place, I'll leave it up to you.

Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
@seanyoung
Copy link
Contributor Author

As discussed in the standup, it would be great to have those SAFETY comments, explaining our thoughts around why all those arguments to the LLVM GEP builder are correct. But it's not only in this place, I'll leave it up to you.

What do you think of the SAFETY comments I've just added?

@xermicus
Copy link
Contributor

What do you think of the SAFETY comments I've just added?

Great, this is exactly what I meant, thanks!

Copy link
Contributor

@LucasSte LucasSte left a comment

Choose a reason for hiding this comment

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

I tested it locally and couldn't find any problem. Codewise, everything looks good. I have only one last question.

Should there be an integration test for this feature?

src/emit/expression.rs Outdated Show resolved Hide resolved
seanyoung and others added 2 commits September 28, 2023 08:19
Co-authored-by: Lucas Steuernagel <[email protected]>
Signed-off-by: Sean Young <[email protected]>
Signed-off-by: Sean Young <[email protected]>
@seanyoung
Copy link
Contributor Author

I tested it locally and couldn't find any problem. Codewise, everything looks good. I have only one last question.

Should there be an integration test for this feature?

I've added a simple test.

This will need re-writing for your branch.

There is also a fix for the "blockhash not found" issue (at least it fixes it locally)

@seanyoung seanyoung requested a review from LucasSte September 28, 2023 11:15
@seanyoung seanyoung merged commit 5675a34 into hyperledger-solang:main Sep 28, 2023
16 checks passed
@seanyoung seanyoung deleted the seeds branch September 28, 2023 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
solana The Solana target
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Solana: it should be possible to pass seeds into function
3 participants