diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 20779a2897..7cc6b21a06 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -21,23 +21,22 @@ fn main() -> Result<()> { /// Generates the Go code for the given schema fn generate_go(root: RootSchema) -> Result { - let title = replace_acronyms( - root.schema - .metadata - .as_ref() - .and_then(|m| m.title.as_ref()) - .context("failed to get type name")?, - ); + let title = root + .schema + .metadata + .as_ref() + .and_then(|m| m.title.as_ref()) + .context("failed to get type name")?; let mut types = vec![]; - build_type(&title, &root.schema, &mut types) + build_type(title, &root.schema, &mut types) .with_context(|| format!("failed to generate {title}"))?; // go through additional definitions for (name, additional_type) in &root.definitions { additional_type .object() - .map(|def| build_type(&replace_acronyms(name), def, &mut types)) + .map(|def| build_type(name, def, &mut types)) .and_then(|r| r) .context("failed to generate additional definitions")?; } @@ -107,7 +106,7 @@ pub fn build_struct( let fields = fields.collect::>>()?; Ok(GoStruct { - name: to_pascal_case(name), + name: replace_acronyms(to_pascal_case(name)), docs, fields, }) @@ -121,6 +120,7 @@ pub fn build_enum<'a>( variants: impl Iterator, additional_structs: &mut Vec, ) -> Result { + let name = replace_acronyms(name); let docs = documentation(enm); // go through all fields @@ -131,18 +131,14 @@ pub fn build_enum<'a>( .with_context(|| format!("expected schema object for enum variants of {name}"))?; // analyze the variant - let variant_field = build_enum_variant(v, name, additional_structs) + let variant_field = build_enum_variant(v, &name, additional_structs) .context("failed to extract enum variant")?; anyhow::Ok(variant_field) }); let fields = fields.collect::>>()?; - Ok(GoStruct { - name: name.to_string(), - docs, - fields, - }) + Ok(GoStruct { name, docs, fields }) } /// Tries to extract the name and type of the given enum variant and returns it as a `GoField`. @@ -433,6 +429,16 @@ mod tests { compare_codes!(cosmwasm_std::WasmQuery); } + #[test] + fn messages_work() { + compare_codes!(cosmwasm_std::BankMsg); + compare_codes!(cosmwasm_std::StakingMsg); + compare_codes!(cosmwasm_std::DistributionMsg); + compare_codes!(cosmwasm_std::IbcMsg); + compare_codes!(cosmwasm_std::WasmMsg); + // compare_codes!(cosmwasm_std::GovMsg); // TODO: currently fails because of VoteOption + } + #[test] fn array_item_type_works() { #[cw_serde] @@ -468,4 +474,78 @@ mod tests { }"#, ); } + + #[test] + fn accronym_replacement_works() { + #[cw_serde] + struct IbcStruct { + a: IbcSubStruct, + b: IbcSubEnum, + } + #[cw_serde] + enum IbcEnum { + A(IbcSubStruct), + B(IbcSubEnum), + } + #[cw_serde] + struct IbcSubStruct {} + #[cw_serde] + enum IbcSubEnum { + A(String), + } + + let code = generate_go(cosmwasm_schema::schema_for!(IbcStruct)).unwrap(); + assert_code_eq( + code, + r#" + type IBCStruct struct { + A IBCSubStruct `json:"a"` + B IBCSubEnum `json:"b"` + } + type IBCSubEnum struct { + A string `json:"a,omitempty"` + } + type IBCSubStruct struct { + } + "#, + ); + + let code = generate_go(cosmwasm_schema::schema_for!(IbcEnum)).unwrap(); + assert_code_eq( + code, + r#" + type IBCEnum struct { + A *IBCSubStruct `json:"a,omitempty"` + B *IBCSubEnum `json:"b,omitempty"` + } + type IBCSubEnum struct { + A string `json:"a,omitempty"` + } + type IBCSubStruct struct { + } + "#, + ); + } + + #[test] + fn timestamp_works() { + use cosmwasm_std::Timestamp; + + #[cw_serde] + struct A { + a: Timestamp, + b: Option, + } + + let code = generate_go(cosmwasm_schema::schema_for!(A)).unwrap(); + assert_code_eq( + code, + r#" + type A struct { + A Uint64 `json:"a"` + B *Uint64 `json:"b,omitempty"` + } + "#, + ); + } } diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index 346bc29895..33d5559ab7 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -43,12 +43,12 @@ pub fn schema_object_type( replace_custom_type(title) } else if let Some(reference) = &schema.reference { // if it has a reference, strip the path and use that - replace_custom_type( + replace_custom_type(&replace_acronyms( reference .split('/') .last() .expect("split should always return at least one item"), - ) + )) } else if let Some(t) = &schema.instance_type { type_from_instance_type(schema, type_context, t, additional_structs)? } else if let Some(subschemas) = schema.subschemas.as_ref().and_then(|s| s.any_of.as_ref()) { @@ -259,13 +259,19 @@ pub fn documentation(schema: &SchemaObject) -> Option { /// If the given type is not a special type, returns `None`. pub fn custom_type_of(ty: &str) -> Option<&str> { match ty { + "Uint64" => Some("Uint64"), "Uint128" => Some("string"), + "Int64" => Some("Int64"), + "Int128" => Some("string"), "Binary" => Some("[]byte"), "HexBinary" => Some("string"), "Checksum" => Some("Checksum"), "Addr" => Some("string"), "Decimal" => Some("string"), "Decimal256" => Some("string"), + "SignedDecimal" => Some("string"), + "SignedDecimal256" => Some("string"), + "Timestamp" => Some("Uint64"), _ => None, } } diff --git a/packages/go-gen/tests/cosmwasm_std__BankMsg.go b/packages/go-gen/tests/cosmwasm_std__BankMsg.go new file mode 100644 index 0000000000..d94723582e --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__BankMsg.go @@ -0,0 +1,24 @@ +// SendMsg contains instructions for a Cosmos-SDK/SendMsg +// It has a fixed interface here and should be converted into the proper SDK format before dispatching +type SendMsg struct { + Amount []Coin `json:"amount"` + ToAddress string `json:"to_address"` +} + +// BurnMsg will burn the given coins from the contract's account. +// There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. +// Important if a contract controls significant token supply that must be retired. +type BurnMsg struct { + Amount []Coin `json:"amount"` +} + +type BankMsg struct { + Send *SendMsg `json:"send,omitempty"` + Burn *BurnMsg `json:"burn,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} diff --git a/packages/go-gen/tests/cosmwasm_std__DistributionMsg.go b/packages/go-gen/tests/cosmwasm_std__DistributionMsg.go new file mode 100644 index 0000000000..35217b6434 --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__DistributionMsg.go @@ -0,0 +1,32 @@ +// SetWithdrawAddressMsg is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). +// `delegator_address` is automatically filled with the current contract's address. +type SetWithdrawAddressMsg struct { + // Address contains the `delegator_address` of a MsgSetWithdrawAddress + Address string `json:"address"` +} + +// WithdrawDelegatorRewardMsg is translated to a [MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). +// `delegator_address` is automatically filled with the current contract's address. +type WithdrawDelegatorRewardMsg struct { + // Validator contains `validator_address` of a MsgWithdrawDelegatorReward + Validator string `json:"validator"` +} + +// FundCommunityPoolMsg is translated to a [MsgFundCommunityPool](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#LL69C1-L76C2). +// `depositor` is automatically filled with the current contract's address +type FundCommunityPoolMsg struct { + // Amount is the list of coins to be send to the community pool + Amount []Coin `json:"amount"` +} + +type DistributionMsg struct { + SetWithdrawAddress *SetWithdrawAddressMsg `json:"set_withdraw_address,omitempty"` + WithdrawDelegatorReward *WithdrawDelegatorRewardMsg `json:"withdraw_delegator_reward,omitempty"` + FundCommunityPool *FundCommunityPoolMsg `json:"fund_community_pool,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} diff --git a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go new file mode 100644 index 0000000000..e9699c1161 --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go @@ -0,0 +1,47 @@ +type TransferMsg struct { + Amount Coin `json:"amount"` + ChannelID string `json:"channel_id"` + Memo string `json:"memo,omitempty"` // this is not yet in wasmvm, but will be soon + Timeout IBCTimeout `json:"timeout"` + ToAddress string `json:"to_address"` +} +type SendPacketMsg struct { + ChannelID string `json:"channel_id"` + Data []byte `json:"data"` + Timeout IBCTimeout `json:"timeout"` +} +type CloseChannelMsg struct { + ChannelID string `json:"channel_id"` +} + +type IBCMsg struct { + Transfer *TransferMsg `json:"transfer,omitempty"` + SendPacket *SendPacketMsg `json:"send_packet,omitempty"` + CloseChannel *CloseChannelMsg `json:"close_channel,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} + +// IBCTimeout is the timeout for an IBC packet. At least one of block and timestamp is required. +type IBCTimeout struct { + Block *IBCTimeoutBlock `json:"block,omitempty"` // in wasmvm, this does not have "omitempty" + // Nanoseconds since UNIX epoch + Timestamp *Uint64 `json:"timestamp,omitempty"` +} + +// IBCTimeoutBlock Height is a monotonically increasing data type +// that can be compared against another Height for the purposes of updating and +// freezing clients. +// Ordering is (revision_number, timeout_height) +type IBCTimeoutBlock struct { + // block height after which the packet times out. + // the height within the given revision + Height uint64 `json:"height"` + // the version that the client is currently on + // (eg. after reseting the chain this could increment 1 as height drops to 0) + Revision uint64 `json:"revision"` +} diff --git a/packages/go-gen/tests/cosmwasm_std__StakingMsg.go b/packages/go-gen/tests/cosmwasm_std__StakingMsg.go new file mode 100644 index 0000000000..e7cc377d1f --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__StakingMsg.go @@ -0,0 +1,25 @@ +type DelegateMsg struct { + Amount Coin `json:"amount"` + Validator string `json:"validator"` +} +type UndelegateMsg struct { + Amount Coin `json:"amount"` + Validator string `json:"validator"` +} +type RedelegateMsg struct { + Amount Coin `json:"amount"` + DstValidator string `json:"dst_validator"` + SrcValidator string `json:"src_validator"` +} + +type StakingMsg struct { + Delegate *DelegateMsg `json:"delegate,omitempty"` + Undelegate *UndelegateMsg `json:"undelegate,omitempty"` + Redelegate *RedelegateMsg `json:"redelegate,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} diff --git a/packages/go-gen/tests/cosmwasm_std__WasmMsg.go b/packages/go-gen/tests/cosmwasm_std__WasmMsg.go new file mode 100644 index 0000000000..0e7287e61b --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__WasmMsg.go @@ -0,0 +1,115 @@ +// ExecuteMsg is used to call another defined contract on this chain. +// The calling contract requires the callee to be defined beforehand, +// and the address should have been defined in initialization. +// And we assume the developer tested the ABIs and coded them together. +// +// Since a contract is immutable once it is deployed, we don't need to transform this. +// If it was properly coded and worked once, it will continue to work throughout upgrades. +type ExecuteMsg struct { + // ContractAddr is the sdk.AccAddress of the contract, which uniquely defines + // the contract ID and instance ID. The sdk module should maintain a reverse lookup table. + ContractAddr string `json:"contract_addr"` + // Send is an optional amount of coins this contract sends to the called contract + Funds []Coin `json:"funds"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Handle` on the above-defined contract + Msg []byte `json:"msg"` +} + +// InstantiateMsg will create a new contract instance from a previously uploaded CodeID. +// This allows one contract to spawn "sub-contracts". +type InstantiateMsg struct { + // Admin (optional) may be set here to allow future migrations from this address + Admin string `json:"admin,omitempty"` + // CodeID is the reference to the wasm byte code as used by the Cosmos-SDK + CodeID uint64 `json:"code_id"` + // Send is an optional amount of coins this contract sends to the called contract + Funds []Coin `json:"funds"` + // Label is optional metadata to be stored with a contract instance. + Label string `json:"label"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Instantiate` on a new contract with the above-defined CodeID + Msg []byte `json:"msg"` +} + +// Instantiate2Msg will create a new contract instance from a previously uploaded CodeID +// using the predictable address derivation. +type Instantiate2Msg struct { + // Admin (optional) may be set here to allow future migrations from this address + Admin string `json:"admin,omitempty"` + // CodeID is the reference to the wasm byte code as used by the Cosmos-SDK + CodeID uint64 `json:"code_id"` + // Send is an optional amount of coins this contract sends to the called contract + Funds []Coin `json:"funds"` + // Label is optional metadata to be stored with a contract instance. + Label string `json:"label"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Instantiate` on a new contract with the above-defined CodeID + Msg []byte `json:"msg"` + Salt []byte `json:"salt"` +} + +// MigrateMsg will migrate an existing contract from it's current wasm code (logic) +// to another previously uploaded wasm code. It requires the calling contract to be +// listed as "admin" of the contract to be migrated. +type MigrateMsg struct { + // ContractAddr is the sdk.AccAddress of the target contract, to migrate. + ContractAddr string `json:"contract_addr"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Migrate` on the above-defined contract + Msg []byte `json:"msg"` + // NewCodeID is the reference to the wasm byte code for the new logic to migrate to + NewCodeID uint64 `json:"new_code_id"` +} + +// UpdateAdminMsg is the Go counterpart of WasmMsg::UpdateAdmin +// (https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta5/packages/std/src/results/cosmos_msg.rs#L158-L160). +type UpdateAdminMsg struct { + // Admin is the sdk.AccAddress of the new admin. + Admin string `json:"admin"` + // ContractAddr is the sdk.AccAddress of the target contract. + ContractAddr string `json:"contract_addr"` +} + +// ClearAdminMsg is the Go counterpart of WasmMsg::ClearAdmin +// (https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta5/packages/std/src/results/cosmos_msg.rs#L158-L160). +type ClearAdminMsg struct { + // ContractAddr is the sdk.AccAddress of the target contract. + ContractAddr string `json:"contract_addr"` +} + +// The message types of the wasm module. +// +// See https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto +type WasmMsg struct { + // Dispatches a call to another contract at a known address (with known ABI). + // + // This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address. + Execute *ExecuteMsg `json:"execute,omitempty"` + // Instantiates a new contracts from previously uploaded Wasm code. + // + // The contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2. + // + // This is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address. + Instantiate *InstantiateMsg `json:"instantiate,omitempty"` + // Instantiates a new contracts from previously uploaded Wasm code using a predictable address derivation algorithm implemented in [`cosmwasm_std::instantiate2_address`]. + // + // This is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96). `sender` is automatically filled with the current contract's address. `fix_msg` is automatically set to false. + Instantiate2 *Instantiate2Msg `json:"instantiate2,omitempty"` + // Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior. + // + // Only the contract admin (as defined in wasmd), if any, is able to make this call. + // + // This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address. + Migrate *MigrateMsg `json:"migrate,omitempty"` + // Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract. + UpdateAdmin *UpdateAdminMsg `json:"update_admin,omitempty"` + // Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract. + ClearAdmin *ClearAdminMsg `json:"clear_admin,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +}