Skip to content

Commit

Permalink
feat: make withdrawals work properly in conway era
Browse files Browse the repository at this point in the history
  • Loading branch information
refi93 committed Jul 15, 2024
1 parent 388e9e5 commit 29ad9d0
Show file tree
Hide file tree
Showing 17 changed files with 320 additions and 73 deletions.
22 changes: 15 additions & 7 deletions app/frontend/actions/delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,16 @@ export default (store: Store) => {
}
await setPoolInfo(state)
const poolHash = state.shelleyDelegation.selectedPool.poolHash as string
const isStakingKeyRegistered = getSourceAccountInfo(state).shelleyAccountInfo.hasStakingKey
const stakingAddress = getSourceAccountInfo(state).stakingAddress
const {
shelleyAccountInfo: {hasStakingKey: isStakingKeyRegistered, hasVoteDelegation},
stakingAddress,
} = getSourceAccountInfo(state)
const txPlanResult = prepareTxPlan({
poolHash,
stakingAddress,
isStakingKeyRegistered,
txType: TxType.DELEGATE,
hasVoteDelegation,
})
const newState = getState()
if (hasPoolIdentifiersChanged(newState)) {
Expand Down Expand Up @@ -180,15 +183,20 @@ export default (store: Store) => {
}

state = getState()
const sourceAccount = getSourceAccountInfo(state)
const rewards = getSourceAccountInfo(state).shelleyBalances.rewardsAccountBalance as Lovelace
const balance = getSourceAccountInfo(state).balance as Lovelace
const {
accountIndex,
balance,
stakingAddress,
shelleyAccountInfo: {hasVoteDelegation},
shelleyBalances: {rewardsAccountBalance: rewards},
} = getSourceAccountInfo(state)

loadingAction(state, 'Preparing transaction...')
const txPlanResult = prepareTxPlan({
txType: TxType.DEREGISTER_STAKE_KEY,
rewards,
stakingAddress: sourceAccount.stakingAddress,
stakingAddress,
hasVoteDelegation,
})
if (txPlanResult.success === true) {
const summary = {
Expand All @@ -199,7 +207,7 @@ export default (store: Store) => {

setTransactionSummary(getState(), {plan: txPlanResult.txPlan, transactionSummary: summary})
await confirmTransaction(getState(), {
sourceAccountIndex: sourceAccount.accountIndex,
sourceAccountIndex: accountIndex,
txPlan: txPlanResult.txPlan,
txConfirmType: TxType.DEREGISTER_STAKE_KEY,
})
Expand Down
20 changes: 10 additions & 10 deletions app/frontend/actions/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ export default (store: Store) => {
let txAux
try {
if (txPlan) {
txAux = await getWallet()
.getAccount(sourceAccountIndex)
.prepareTxAux(txPlan)
txAux = await getWallet().getAccount(sourceAccountIndex).prepareTxAux(txPlan)
} else {
loadingAction(state, 'Preparing transaction plan...')
await sleep(1000) // wait for plan to be set in case of unfortunate timing
Expand Down Expand Up @@ -220,12 +218,8 @@ export default (store: Store) => {
let sendResponse
try {
assert(txSummary.plan != null)
const txAux = await getWallet()
.getAccount(sourceAccountIndex)
.prepareTxAux(txSummary.plan)
const signedTx = await getWallet()
.getAccount(sourceAccountIndex)
.signTxAux(txAux)
const txAux = await getWallet().getAccount(sourceAccountIndex).prepareTxAux(txSummary.plan)
const signedTx = await getWallet().getAccount(sourceAccountIndex).signTxAux(txAux)
if (isHwWallet(cryptoProviderType)) {
setState({waitingHwWalletOperation: null})
stopLoadingAction(state)
Expand Down Expand Up @@ -330,7 +324,13 @@ export default (store: Store) => {
// TODO: rewards should be of type Lovelace
const rewards = getSourceAccountInfo(state).shelleyBalances.rewardsAccountBalance as Lovelace
const stakingAddress = getSourceAccountInfo(state).stakingAddress
const txPlanResult = prepareTxPlan({rewards, stakingAddress, txType: TxType.WITHDRAW})
const hasVoteDelegation = getSourceAccountInfo(state).shelleyAccountInfo.hasVoteDelegation
const txPlanResult = prepareTxPlan({
rewards,
stakingAddress,
hasVoteDelegation,
txType: TxType.WITHDRAW,
})
// TODO: balance should be of type Lovelace
const balance = getSourceAccountInfo(state).balance as Lovelace

Expand Down
5 changes: 5 additions & 0 deletions app/frontend/helpers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ export function getCexplorerUrl() {
SANCHONET: 'https://sancho.cexplorer.io',
}[ADALITE_CONFIG.ADALITE_NETWORK]
}

// https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript
export const safeAssertUnreachable = (x: never): never => {
throw new Error(`Unreachable switch case:${x}`)
}
1 change: 1 addition & 0 deletions app/frontend/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ const initialState: State = {
currentEpoch: 0,
delegation: {},
hasStakingKey: false,
hasVoteDelegation: false,
rewards: '0',
rewardDetails: {
upcoming: null,
Expand Down
5 changes: 5 additions & 0 deletions app/frontend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export enum CertificateType {
STAKING_KEY_DEREGISTRATION = 1,
DELEGATION = 2,
STAKEPOOL_REGISTRATION = 3,
VOTE_DELEGATION = 9,
}

export enum CryptoProviderFeature {
Expand Down Expand Up @@ -171,6 +172,7 @@ export type AccountInfo = {
currentEpoch: number
delegation: any
hasStakingKey: boolean
hasVoteDelegation: boolean
rewards: string
rewardDetails: {
upcoming: any
Expand Down Expand Up @@ -267,13 +269,15 @@ export type WithdrawRewardsTxPlanArgs = {
txType: TxType.WITHDRAW
rewards: Lovelace
stakingAddress: Address
hasVoteDelegation: boolean
}

export type DelegateAdaTxPlanArgs = {
txType: TxType.DELEGATE
poolHash: string
isStakingKeyRegistered: boolean
stakingAddress: Address
hasVoteDelegation: boolean
}

export type PoolOwnerTxPlanArgs = {
Expand All @@ -285,6 +289,7 @@ export type DeregisterStakingKeyTxPlanArgs = {
txType: TxType.DEREGISTER_STAKE_KEY
rewards: Lovelace
stakingAddress: Address
hasVoteDelegation: boolean
}

export type VotingRegistrationTxPlanArgs = {
Expand Down
18 changes: 7 additions & 11 deletions app/frontend/wallet/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,10 @@ const Account = ({config, cryptoProvider, blockchainExplorer, accountIndex}: Acc

async function getBalance() {
const {legacy, base} = await myAddresses.discoverAllAddresses()
const {
coins: nonStakingBalance,
tokenBundle: nonStakingTokenBundle,
} = await blockchainExplorer.getBalance(legacy)
const {
coins: baseAddressBalance,
tokenBundle: stakingTokenBundle,
} = await blockchainExplorer.getBalance(base)
const {coins: nonStakingBalance, tokenBundle: nonStakingTokenBundle} =
await blockchainExplorer.getBalance(legacy)
const {coins: baseAddressBalance, tokenBundle: stakingTokenBundle} =
await blockchainExplorer.getBalance(base)
return {
tokenBalance: aggregateTokenBundles([nonStakingTokenBundle, stakingTokenBundle]),
baseAddressBalance,
Expand Down Expand Up @@ -436,9 +432,8 @@ const Account = ({config, cryptoProvider, blockchainExplorer, accountIndex}: Acc

async function getStakingInfo(validStakepoolDataProvider: StakepoolDataProvider) {
const stakingAddressHex = bechAddressToHex(await myAddresses.getStakingAddress())
const {nextRewardDetails, ...accountInfo} = await blockchainExplorer.getStakingInfo(
stakingAddressHex
)
const {nextRewardDetails, ...accountInfo} =
await blockchainExplorer.getStakingInfo(stakingAddressHex)
const rewardDetails = await blockchainExplorer.getRewardDetails(
nextRewardDetails,
accountInfo.delegation.poolHash,
Expand All @@ -448,6 +443,7 @@ const Account = ({config, cryptoProvider, blockchainExplorer, accountIndex}: Acc

return {
...accountInfo,
hasVoteDelegation: accountInfo.voteDelegation != null,
rewardDetails,
value: new BigNumber(accountInfo.rewards || 0) as Lovelace,
}
Expand Down
5 changes: 5 additions & 0 deletions app/frontend/wallet/backend-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ export type StakingInfoResponse = {
hasStakingKey: boolean
rewards: string
nextRewardDetails: Array<NextRewardDetail>
voteDelegation: {
dRepHash: string
isAlwaysAbstain: boolean
isAlwaysNoConfidence: boolean
} | null
}

export type BestSlotResponse = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
TxOutput,
TxShelleyWitness,
} from '../types'
import {encodeCbor} from '../helpers/cbor'
import {safeAssertUnreachable} from '../../helpers/common'

import {
CardanoAssetGroup,
Expand All @@ -33,7 +35,6 @@ import {CborizedCliWitness, TxAux, TxSigned} from './types'
import {hasRequiredVersion} from './helpers/version-check'
import {BITBOX02_ERRORS, BITBOX02_VERSIONS} from '../constants'
import {ShelleySignedTransactionStructured, cborizeTxWitnesses} from './shelley-transaction'
import {encodeCbor} from '../helpers/cbor'
import {orderTokenBundle} from '../helpers/tokenFormater'
import debugLog from '../../helpers/debugLog'
import CachedDeriveXpubFactory from '../helpers/CachedDeriveXpubFactory'
Expand Down Expand Up @@ -202,8 +203,12 @@ const ShelleyBitBox02CryptoProvider = async ({
throw new UnexpectedError(UnexpectedErrorReason.UnsupportedOperationError, {
message: 'Stakepool registration not supported',
})
case CertificateType.VOTE_DELEGATION:
throw new UnexpectedError(UnexpectedErrorReason.UnsupportedOperationError, {
message: 'Vote delegation not supported',
})
default:
throw new UnexpectedError(UnexpectedErrorReason.InvalidCertificateType)
return safeAssertUnreachable(certificate)
}
}

Expand Down
29 changes: 28 additions & 1 deletion app/frontend/wallet/shelley/shelley-ledger-crypto-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ import {
TxStakepoolRegistrationCert,
TxStakingKeyDeregistrationCert,
TxStakingKeyRegistrationCert,
TxVoteDelegationCert,
TxWithdrawal,
TxDRepType,
} from '../types'
import {TxSigned, TxAux, CborizedCliWitness, FinalizedAuxiliaryDataTx} from './types'
import {orderTokenBundle} from '../helpers/tokenFormater'
Expand All @@ -63,6 +65,7 @@ import {TxRelayType, TxStakepoolOwner, TxStakepoolRelay} from './helpers/poolCer
import assertUnreachable from '../../helpers/assertUnreachable'
import * as assert from 'assert'
import {encodeCbor} from '../helpers/cbor'
import {safeAssertUnreachable} from '../../helpers/common'

let _activeTransport: Transport | null
const getLedgerTransport = async (ledgerTransportType: LedgerTransportType): Promise<Transport> => {
Expand Down Expand Up @@ -396,6 +399,28 @@ const ShelleyLedgerCryptoProvider = async ({
}
}

type SupportedTxVoteDelegationCert = TxVoteDelegationCert & {
dRep: {type: TxDRepType.ALWAYS_ABSTAIN}
}
function prepareVoteDelegationCertificate(
certificate: SupportedTxVoteDelegationCert,
path: BIP32Path
): LedgerTypes.Certificate {
assert(certificate.dRep.type === TxDRepType.ALWAYS_ABSTAIN)
return {
type: LedgerTypes.CertificateType.VOTE_DELEGATION,
params: {
stakeCredential: {
type: LedgerTypes.CredentialParamsType.KEY_PATH,
keyPath: path,
},
dRep: {
type: LedgerTypes.DRepParamsType.ABSTAIN,
},
},
}
}

function prepareCertificate(
certificate: TxCertificate,
addressToAbsPathMapper: AddressToPathMapper
Expand All @@ -410,8 +435,10 @@ const ShelleyLedgerCryptoProvider = async ({
return prepareDelegationCertificate(certificate, path)
case CertificateType.STAKEPOOL_REGISTRATION:
return prepareStakepoolRegistrationCertificate(certificate, path)
case CertificateType.VOTE_DELEGATION:
return prepareVoteDelegationCertificate(certificate, path)
default:
throw new UnexpectedError(UnexpectedErrorReason.InvalidCertificateType)
return safeAssertUnreachable(certificate)
}
}

Expand Down
24 changes: 22 additions & 2 deletions app/frontend/wallet/shelley/shelley-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
TxStakingKeyDeregistrationCert,
TxStakingKeyRegistrationCert,
TxWithdrawal,
TxVoteDelegationCert,
TxDRepType,
} from '../types'
import {
CborizedTxAmount,
Expand All @@ -40,12 +42,14 @@ import {
TxSigned,
CborizedCliWitness,
CborizedVotingRegistrationMetadata,
CborizedVoteDelegationCert,
} from './types'
import {CertificateType, HexString, TokenBundle} from '../../types'
import {UnexpectedError, UnexpectedErrorReason} from '../../errors'
import {ipv4AddressToBuf, ipv6AddressToBuf, TxRelayType} from './helpers/poolCertificateUtils'
import {orderTokenBundle} from '../helpers/tokenFormater'
import BigNumber from 'bignumber.js'
import {safeAssertUnreachable} from '../../helpers/common'
import * as assert from 'assert'

function ShelleyTxAux({
inputs,
Expand Down Expand Up @@ -239,6 +243,20 @@ function cborizeStakepoolRegistrationCert(
]
}

type SupportedDelegationCert = TxVoteDelegationCert & {dRep: {type: TxDRepType.ALWAYS_ABSTAIN}}
function cborizeVoteDelegationCert(
certificate: SupportedDelegationCert
): CborizedVoteDelegationCert {
assert(certificate.dRep.type === TxDRepType.ALWAYS_ABSTAIN)
const stakingKeyHash: Buffer = bech32.decode(certificate.stakingAddress).data.slice(1)
const stakeCredential: CborizedTxStakeCredential = [
TxStakeCredentialType.ADDR_KEYHASH,
stakingKeyHash,
]

return [TxCertificateKey.VOTE_DELEGATION, stakeCredential, [TxDRepType.ALWAYS_ABSTAIN]]
}

function cborizeTxCertificates(certificates: TxCertificate[]): CborizedTxCertificate[] {
const txCertificates = certificates.map((certificate) => {
switch (certificate.type) {
Expand All @@ -250,8 +268,10 @@ function cborizeTxCertificates(certificates: TxCertificate[]): CborizedTxCertifi
return cborizeDelegationCert(certificate)
case CertificateType.STAKEPOOL_REGISTRATION:
return cborizeStakepoolRegistrationCert(certificate)
case CertificateType.VOTE_DELEGATION:
return cborizeVoteDelegationCert(certificate)
default:
throw new UnexpectedError(UnexpectedErrorReason.InvalidCertificateType)
return safeAssertUnreachable(certificate)
}
})
return txCertificates
Expand Down
Loading

0 comments on commit 29ad9d0

Please sign in to comment.