hyperledger / solang

Solidity Compiler for Solana and Polkadot
https://solang.readthedocs.io/
Apache License 2.0
1.23k stars 206 forks source link

The PDA address cannot be transferred, what should I do? #1605

Closed jimlon-xyz closed 7 months ago

jimlon-xyz commented 7 months ago

PDA address cannot transfer out lamports, what should I do? This contract program setting is really uncomfortable. If it can be like EVM' solidity features, it will be very convenient and developers will also like it very much.

` // ...

@mutableAccount(pdaAccount)
@mutableSigner(sender)
function mint(string memory text) external returns(bool) {
    // ...
    // Here is working
    SystemInstruction.transfer(tx.accounts.sender.key, tx.accounts.pdaAccount.key, _mint_fee);

    bool failure = true;
    if (failure)  {
        SystemInstruction.transfer(tx.accounts.pdaAccount.key, tx.accounts.sender.key, _mint_fee / 2);
    }

    return true;
}

@mutableSigner(sender)
function for_back() external returns(bool) {
    address pda_account = get_pda_account();
    return true;
}

// ...

`

LucasSte commented 7 months ago

What error do you have when you execute your contract?

jimlon-xyz commented 7 months ago

What error do you have when you execute your contract?

Execution failed and instruction missing

LucasSte commented 7 months ago

Can you provide us with a minimal reproducible example of your error containing a Solidity contract, Typescript code and the command to execute?

From the Solidity code you provided I cannot see any problem, so there might be an issue in how you are creating the PDA.

When I asked the error message, I wanted to know exactly what your terminal says. instruction missing is not an error Solana can produce, and I guess you wanted to say account missing.

jimlon-xyz commented 7 months ago

Can you provide us with a minimal reproducible example of your error containing a Solidity contract, Typescript code and the command to execute?

From the Solidity code you provided I cannot see any problem, so there might be an issue in how you are creating the PDA.

When I asked the error message, I wanted to know exactly what your terminal says. instruction missing is not an error Solana can produce, and I guess you wanted to say account missing.

` import {try_find_program_address} from 'solana'; import "./lib/system_instruction.sol";

@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC") contract sol_nft {

uint64 private _mint_fee = 1 * 1000000000;
uint64 private lucky_awards = 5 * 1000000000;

mapping(uint => address) private _tokenOwners;

@space(1024 * 8)
@payer(payer)
constructor() {
    print("Hello, World!");
}

function get_pda_account() public view returns(address) {
    (address addr,) = try_find_program_address(["sol_nft"], address(this));
    return addr;
}

@mutableAccount(pdaAccount)
@mutableSigner(sender)
function mint(string memory text) external returns(bool) {
    address sender = tx.accounts.sender.key;
    uint256 tokenId = uint256(keccak256(abi.encodePacked(text)));
    require(_tokenOwners[tokenId] == address(0), "Inscription key duplicated");

    print("signer.sender={}".format(sender));
    _tokenOwners[tokenId] = sender;

    // Current signer send to PDA account mint fee
    SystemInstruction.transfer(tx.accounts.sender.key, tx.accounts.pdaAccount.key, _mint_fee);

    uint randomNumber = uint256(keccak256(abi.encodePacked(address(this), block.number, block.timestamp, sender, tokenId))) % 100;

    if (randomNumber > 50) {
        // lucky awards
        // from PDA acount send to current signer account
        SystemInstruction.transfer(tx.accounts.pdaAccount.key, tx.accounts.sender.key, lucky_awards);
    }

    return true;
}

} `

` import as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { SolNft } from "../target/types/sol_nft"; import as borsh from "borsh";

describe("sol_nft", () => { // Configure the client to use the local cluster. const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider);

const dataAccount = anchor.web3.Keypair.generate()
const wallet = provider.wallet;

const program = anchor.workspace.SolNft as Program<SolNft>;

it("Is initialized!", async () => {

    let tx, programIx

    console.log( "wallet.address=", wallet.publicKey.toBase58() )
    console.log( "dataAccount.address=", dataAccount.publicKey.toBase58() )
    console.log( "wallet.before=", await provider.connection.getBalance(wallet.publicKey, { commitment: "confirmed" }) )

    tx = await program.methods
      .new()
      .accounts({ dataAccount: dataAccount.publicKey })
      .signers([dataAccount])
      .rpc({ commitment: "confirmed" })
    console.log("tx1=", tx)

    console.log("wallet.after=", await provider.connection.getBalance(wallet.publicKey, { commitment: "confirmed" }) )

    let pdaAccount = await program.methods
      .getPdaAccount()
      .accounts({ dataAccount: dataAccount.publicKey })
      .view()
    console.log("pda_account=", pdaAccount.toBase58())

    // Cannot send out lamports from the PDA to mutable signer account, Execute failure
    tx = await program.methods
      .mint("hello")
      .accounts({ dataAccount: dataAccount.publicKey, pdaAccount })
      .rpc({ commitment: "confirmed" })

    console.log("tx2=", tx)

});

});

`

LucasSte commented 7 months ago

You've declared your solidity function like this:

@mutableAccount(pdaAccount)
@mutableSigner(sender)
function mint(string memory text) external returns(bool)

When you call it from Typescript, you must pass the accounts in the call:

tx = await program.methods
      .mint("hello")
      .accounts({ dataAccount: dataAccount.publicKey, 
                          pdaAccount: pdaAccount,
                          sender: YOUR_SENDER_HERE})
      .signer([SENDER_KEYPAIR])
      .rpc({ commitment: "confirmed" })

When you transfer tokens out of a PDA, you must sign the transaction with the PDA seeds. You'll need to modify SystemInstruction.transfer, so that the external call receives your seeds here:

    if (randomNumber > 50) {
        // lucky awards
        // from PDA acount send to current signer account
        SystemInstruction.transfer(tx.accounts.pdaAccount.key, tx.accounts.sender.key, lucky_awards);
    }

There is no way we could make this look like Ethereum, because Ethereum does not have PDAs or seeds. We are open for suggestions if you can think of a better way to handle PDAs more closely to Ethereum.

jimlon-xyz commented 7 months ago

try_find_program_address(["sol_nft"], address(this))

The code cannot continue to execute at this line and seems to have reached a deadlock. This is the function that creates the PDA address try_find_program_address(["sol_nft"], address(this));

What do I need to do to be able to transfer money and carry seeds? Is there any sample code that I don't know how to write?

Samples: Is it correct? SystemInstruction.transfer{seed: bytes("sol_nft")}(tx.accounts.pdaAccount.key, tx.accounts.sender.key, lucky_awards);

LucasSte commented 7 months ago

try_find_program_address(["sol_nft"], address(this))

There is no deadlock in your program. try_find_program_address calls create_program_address iteratively until it can find an off-curve address: https://edge.docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses. That function is slow for the blockchain and often used directly in Typescript.

The code cannot continue to execute at this line and seems to have reached a deadlock. This is the function that creates the PDA address try_find_program_address(["sol_nft"], address(this));

What do I need to do to be able to transfer money and carry seeds? Is there any sample code that I don't know how to write?

Samples: Is it correct? SystemInstruction.transfer{seed: bytes("sol_nft")}(tx.accounts.pdaAccount.key, tx.accounts.sender.key, lucky_awards);

No, it is not. Here is an example: https://github.com/valory-xyz/autonolas-registries/blob/2ce07e89e070230675727cf9c91d223d5f8e8ada/integrations/solana/contracts/SystemInstruction.sol#L57-L66

jimlon-xyz commented 7 months ago

try_find_program_address(["sol_nft"], address(this))

There is no deadlock in your program. try_find_program_address calls create_program_address iteratively until it can find an off-curve address: https://edge.docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses. That function is slow for the blockchain and often used directly in Typescript.

The code cannot continue to execute at this line and seems to have reached a deadlock. This is the function that creates the PDA address try_find_program_address(["sol_nft"], address(this)); What do I need to do to be able to transfer money and carry seeds? Is there any sample code that I don't know how to write? Samples: Is it correct? SystemInstruction.transfer{seed: bytes("sol_nft")}(tx.accounts.pdaAccount.key, tx.accounts.sender.key, lucky_awards);

No, it is not. Here is an example: https://github.com/valory-xyz/autonolas-registries/blob/2ce07e89e070230675727cf9c91d223d5f8e8ada/integrations/solana/contracts/SystemInstruction.sol#L57-L66

That’s it! thank you so much! I've been looking for it, thank you sooooooooooo much! ! !