-
Notifications
You must be signed in to change notification settings - Fork 274
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
feat(cosmos): Add support for contract call transaction #3691
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { TransactionType } from '@bitgo/sdk-core'; | ||
import { BaseCoin as CoinConfig } from '@bitgo/statics'; | ||
|
||
import * as constants from './constants'; | ||
import { ExecuteContractMessage } from './iface'; | ||
import { CosmosTransactionBuilder } from './transactionBuilder'; | ||
import { CosmosUtils } from './utils'; | ||
|
||
export class ContractCallBuilder extends CosmosTransactionBuilder { | ||
protected _utils: CosmosUtils; | ||
|
||
constructor(_coinConfig: Readonly<CoinConfig>, utils: CosmosUtils) { | ||
super(_coinConfig, utils); | ||
this._utils = utils; | ||
} | ||
|
||
protected get transactionType(): TransactionType { | ||
return TransactionType.ContractCall; | ||
} | ||
|
||
/** @inheritdoc */ | ||
messages(messages: ExecuteContractMessage[]): this { | ||
this._messages = messages.map((executeContractMessage) => { | ||
this._utils.validateExecuteContractMessage(executeContractMessage); | ||
return { | ||
typeUrl: constants.executeContractMsgTypeUrl, | ||
value: executeContractMessage, | ||
}; | ||
}); | ||
return this; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,12 +21,14 @@ import { Coin, defaultRegistryTypes } from '@cosmjs/stargate'; | |
import BigNumber from 'bignumber.js'; | ||
import { SignDoc, TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; | ||
import { Any } from 'cosmjs-types/google/protobuf/any'; | ||
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; | ||
|
||
import * as crypto from 'crypto'; | ||
import * as constants from './constants'; | ||
import { | ||
CosmosLikeTransaction, | ||
DelegateOrUndelegeteMessage, | ||
ExecuteContractMessage, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use CosmosTransactionMessage here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can't, schema is different |
||
FeeData, | ||
MessageData, | ||
SendMessage, | ||
|
@@ -35,7 +37,12 @@ import { | |
import { CosmosKeyPair as KeyPair } from './keyPair'; | ||
|
||
export class CosmosUtils implements BaseUtils { | ||
private registry = new Registry([...defaultRegistryTypes]); | ||
private registry; | ||
|
||
constructor() { | ||
this.registry = new Registry([...defaultRegistryTypes]); | ||
this.registry.register(constants.executeContractMsgTypeUrl, MsgExecuteContract); | ||
} | ||
|
||
/** @inheritdoc */ | ||
isValidBlockId(hash: string): boolean { | ||
|
@@ -260,6 +267,26 @@ export class CosmosUtils implements BaseUtils { | |
}); | ||
} | ||
|
||
/** | ||
* Returns the array of MessageData[] from the decoded transaction | ||
* @param {DecodedTxRaw} decodedTx | ||
* @returns {MessageData[]} Execute contract transaction message data | ||
*/ | ||
getExecuteContractMessageDataFromDecodedTx(decodedTx: DecodedTxRaw): MessageData[] { | ||
return decodedTx.body.messages.map((message) => { | ||
const value = this.registry.decode(message); | ||
return { | ||
value: { | ||
sender: value.sender, | ||
contract: value.contract, | ||
msg: value.msg, | ||
funds: value.funds, | ||
}, | ||
typeUrl: message.typeUrl, | ||
}; | ||
}); | ||
} | ||
|
||
/** | ||
* Determines bitgo transaction type based on cosmos proto type url | ||
* @param {string} typeUrl | ||
|
@@ -275,6 +302,8 @@ export class CosmosUtils implements BaseUtils { | |
return TransactionType.StakingDeactivate; | ||
case constants.withdrawDelegatorRewardMsgTypeUrl: | ||
return TransactionType.StakingWithdraw; | ||
case constants.executeContractMsgTypeUrl: | ||
return TransactionType.ContractCall; | ||
default: | ||
return undefined; | ||
} | ||
|
@@ -473,19 +502,23 @@ export class CosmosUtils implements BaseUtils { | |
switch (type) { | ||
case TransactionType.Send: { | ||
const value = messageData.value as SendMessage; | ||
this.isObjPropertyNull(value, ['toAddress', 'fromAddress']); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we removing this null check ?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is being done in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you check this function's path - |
||
this.validateSendMessage(value); | ||
break; | ||
} | ||
case TransactionType.StakingActivate: | ||
case TransactionType.StakingDeactivate: { | ||
const value = messageData.value as DelegateOrUndelegeteMessage; | ||
this.isObjPropertyNull(value, ['validatorAddress', 'delegatorAddress']); | ||
this.validateAmount(value.amount); | ||
this.validateDelegateOrUndelegateMessage(value); | ||
break; | ||
} | ||
case TransactionType.StakingWithdraw: { | ||
const value = messageData.value as WithdrawDelegatorRewardsMessage; | ||
this.isObjPropertyNull(value, ['validatorAddress', 'delegatorAddress']); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above. |
||
this.validateWithdrawRewardsMessage(value); | ||
break; | ||
} | ||
case TransactionType.ContractCall: { | ||
const value = messageData.value as ExecuteContractMessage; | ||
this.validateExecuteContractMessage(value); | ||
break; | ||
} | ||
default: | ||
|
@@ -591,6 +624,8 @@ export class CosmosUtils implements BaseUtils { | |
sendMessageData = this.getDelegateOrUndelegateMessageDataFromDecodedTx(decodedTx); | ||
} else if (type === TransactionType.StakingWithdraw) { | ||
sendMessageData = this.getWithdrawRewardsMessageDataFromDecodedTx(decodedTx); | ||
} else if (type === TransactionType.ContractCall) { | ||
sendMessageData = this.getExecuteContractMessageDataFromDecodedTx(decodedTx); | ||
} else { | ||
throw new Error('Transaction type not supported: ' + typeUrl); | ||
} | ||
|
@@ -671,6 +706,35 @@ export class CosmosUtils implements BaseUtils { | |
isValidAddress(address: string): boolean { | ||
throw new NotImplementedError('isValidAddress not implemented'); | ||
} | ||
|
||
/** | ||
* Validates if the address matches with regex @see contractAddressRegex | ||
* @param {string} address | ||
* @returns {boolean} - the validation result | ||
*/ | ||
isValidContractAddress(address: string): boolean { | ||
throw new NotImplementedError('isValidContractAddress not implemented'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's implement using the length comparison and add ToDo to implement with Regex. Else it will be blocker in the platform side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is there in extended classes |
||
} | ||
|
||
/** | ||
* Validates a execute contract message | ||
* @param {ExecuteContractMessage} message - The execute contract message to validate | ||
* @throws {InvalidTransactionError} Throws an error if the message is invalid | ||
*/ | ||
validateExecuteContractMessage(message: ExecuteContractMessage) { | ||
if (!message.contract || !this.isValidContractAddress(message.contract)) { | ||
throw new InvalidTransactionError(`Invalid ExecuteContractMessage contract address: ` + message.contract); | ||
} | ||
if (!message.sender || !this.isValidAddress(message.sender)) { | ||
throw new InvalidTransactionError(`Invalid ExecuteContractMessage sender address: ` + message.sender); | ||
} | ||
if (!message.msg) { | ||
throw new InvalidTransactionError(`Invalid ExecuteContractMessage msg: ` + message.msg); | ||
} | ||
if (message.funds) { | ||
this.validateAmountData(message.funds); | ||
} | ||
} | ||
} | ||
|
||
const utils = new CosmosUtils(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,9 @@ export const delegateMsgTypeUrl = '/cosmos.staking.v1beta1.MsgDelegate'; | |
export const undelegateMsgTypeUrl = '/cosmos.staking.v1beta1.MsgUndelegate'; | ||
export const withdrawDelegatorRewardMsgTypeUrl = '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward'; | ||
export const validDenoms = ['natom', 'uatom', 'matom', 'atom']; | ||
export const accountAddressRegex = /^(cosmos)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const validatorAddressRegex = /^(cosmosvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const accountAddressRegex = /^(cosmos)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; | ||
export const validatorAddressRegex = /^(cosmosvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; | ||
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What changed here ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. length, for account address it would always be 38 character long(excluding prefix) |
||
export const contractAddressRegex = /^(cosmos)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const UNAVAILABLE_TEXT = 'UNAVAILABLE'; | ||
export const GAS_AMOUNT = '100000'; | ||
export const GAS_LIMIT = 200000; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export const validDenoms = ['nbld', 'ubld', 'mbld', 'bld']; | ||
export const accountAddressRegex = /^(agoric)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const validatorAddressRegex = /^(agoricvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const accountAddressRegex = /^(agoric)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; | ||
export const validatorAddressRegex = /^(agoricvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; | ||
export const contractAddressRegex = /^(agoric)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export const validDenoms = ['nhash', 'uhash', 'mhash', 'hash']; | ||
export const accountAddressRegex = /^(tp|pb)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const validatorAddressRegex = /^(tpvaloper|pbvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; | ||
export const accountAddressRegex = /^(tp|pb)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; | ||
export const validatorAddressRegex = /^(tpvaloper|pbvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; | ||
export const contractAddressRegex = /^(tp|pb)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']+)$/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Contract interaction support the amount, anyway that will not require in our case but let's keep in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, it is under funds and didn't want to update current amount validation for it