So, I am trying to remove liquidity from a pancake v2 pair and I do not understand why this is not working. I am looking at the code, and I've triple checked every value, I don' think I am doing anything wrong. I need some help.

Here is my code:

    def to_32byte_hex(self, val):
        return to_hex(to_bytes(val).rjust(32, b'\0'))

    def build_remove_with_permit(self, token_address: ChecksumAddress):
    :param token_address: liquidity pair token
    :return: raw transaction dict
    # returns the correct values, confirmed because this 
    # function works on other pairs where I can call removeLiqudi
    token1, token2, liquidity_balance, minOutToken0, minOutToken1 = self.get_liq_remove_info(token_address)
    lp_contract = w3.eth.contract(token_address, abi=UNIV2_LP_ABI)
    max_fee_per_priority, max_fee_per_gas, xx = gas_estimator(w3, self.account.address, self.account.address, 0,
    # pancakeswap v2 is a uniswap v2 fork
    router_contract = w3.eth.contract(self.router, abi=UNIV2_ABI)
    # Prepare parameters
    # Getting these values directly from the pair contract ... 
    PERMIT_TYPEHASH = lp_contract.functions.PERMIT_TYPEHASH().call()
    DOMAIN_SEPARATOR = lp_contract.functions.DOMAIN_SEPARATOR().call()
    x19 = '\x19\x01'  # and this is also right from the contract source
    owner = self.account.address  # caller is me
    spender = self.router  # spender is router 
    value = liquidity_balance  # amount of liq tokens to burn
    nonce = lp_contract.functions.nonces(self.account.address).call()  # my nonce on the contract
    deadline = int(time.time() + 300)  # 5 minutes from now

    # Encode and hash the parameters
    x = eth_abi.encode(['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'],
                            [PERMIT_TYPEHASH, to_checksum_address(owner), to_checksum_address(spender), value, nonce, deadline])
    # print(x)
    y = w3.solidity_keccak(['string', 'bytes32', 'bytes32'], [x19, DOMAIN_SEPARATOR, x])
    # print(y)
    signature = w3.eth.account.signHash(y, private_key=self.account.key)
    v, r, s = signature.v, self.to_32byte_hex(signature.r), self.to_32byte_hex(signature.s)
    # print(v,r,s)
    # Here I am just calling `permit` on the pair, to test whether or not I got it 
    # right, which for some reason, I have not!
        lp_contract.functions.permit(self.account.address, self.router, liquidity_balance, deadline, v, r, s).call(
            {'from': self.account.address})
    except ContractLogicError as err:
        print('[!] Contract reverted with: %s' % err)
        return {}

    raw_txn = {
        "from": self.account.address,
        "gas": 300000,  # 200000
        'maxPriorityFeePerGas': to_wei(max_fee_per_priority, 'gwei'),
        'maxFeePerGas': to_wei(max_fee_per_gas, 'gwei'),
        "to": self.router,
        "value": 0,
        "data": router_contract.encodeABI('removeLiquidityWithPermit',
                                          args=(to_checksum_address(token1), to_checksum_address(token2),
                                                liquidity_balance, minOutToken0, minOutToken1,
                                                self.account.address, deadline, True, v, r, s)),
        "nonce": w3.eth.get_transaction_count(self.account.address),
        "chainId": w3.eth.chain_id
    return raw_txn

Can anyone tell me what is wrong with my code? As far as I can tell, I am doing this correctly.

I figure that if I can successfully call permit() on the pair contract, I should be able to then call removeLiquidityWithPermit(). So, in the code I shared, I am testing that with call(). This is the permit function on the LP pair contract:

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
        require(deadline >= block.timestamp, 'Pancake: EXPIRED');
        bytes32 digest = keccak256(
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress != address(0) && recoveredAddress == owner, 'Pancake: INVALID_SIGNATURE');
        _approve(owner, spender, value);

I am getting:

[!] Contract reverted with: execution reverted: Pancake: INVALID_SIGNATURE

Can anyone tell me why? Maybe I am missing something? Thanks ...

  • If I were you to debug this I would create a test solidity view function to just return the digest and compare with your y in python. It clearly says the signature is wrong, likely the digest computation is wrong
    – minhhn2910
    Aug 16 at 15:32


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.