use crate::common;
use aptos_types::transaction::{
ArgumentABI, ScriptABI, ScriptFunctionABI, TransactionScriptABI, TypeArgumentABI,
};
use move_deps::move_core_types::{
account_address::AccountAddress,
language_storage::{ModuleId, TypeTag},
};
use serde_generate::{
golang,
indent::{IndentConfig, IndentedWriter},
CodeGeneratorConfig,
};
use heck::CamelCase;
use std::{
collections::BTreeMap,
io::{Result, Write},
path::PathBuf,
};
pub fn output(
out: &mut dyn Write,
serde_module_path: Option<String>,
aptos_module_path: Option<String>,
package_name: String,
abis: &[ScriptABI],
) -> Result<()> {
let mut emitter = GoEmitter {
out: IndentedWriter::new(out, IndentConfig::Tab),
serde_module_path,
aptos_module_path,
package_name,
};
let abis_vec = abis
.iter()
.cloned()
.filter(|abi| {
if let ScriptABI::ScriptFunction(sf) = abi {
sf.module_name().name().as_str() != "code" && sf.name() != "publish_package_txn"
} else {
true
}
})
.collect::<Vec<_>>();
let abis = abis_vec.as_slice();
emitter.output_script_call_enum_with_imports(abis)?;
emitter.output_encode_method(abis)?;
emitter.output_transaction_script_decode_method()?;
emitter.output_script_function_decode_method()?;
for abi in abis {
match abi {
ScriptABI::TransactionScript(abi) => {
emitter.output_transaction_script_encoder_function(abi)?
}
ScriptABI::ScriptFunction(abi) => {
emitter.output_script_function_encoder_function(abi)?
}
};
}
for abi in abis {
match abi {
ScriptABI::TransactionScript(abi) => {
emitter.output_transaction_script_decoder_function(abi)?
}
ScriptABI::ScriptFunction(abi) => {
emitter.output_script_function_decoder_function(abi)?
}
};
}
for abi in abis {
emitter.output_code_constant(abi)?;
}
emitter.output_transaction_script_decoder_map(&common::transaction_script_abis(abis))?;
emitter.output_script_function_decoder_map(&common::script_function_abis(abis))?;
emitter.output_encoding_helpers(abis)?;
emitter.output_decoding_helpers(&common::filter_transaction_scripts(abis))?;
Ok(())
}
struct GoEmitter<T> {
out: IndentedWriter<T>,
serde_module_path: Option<String>,
aptos_module_path: Option<String>,
package_name: String,
}
impl<T> GoEmitter<T>
where
T: Write,
{
fn output_script_call_enum_with_imports(&mut self, abis: &[ScriptABI]) -> Result<()> {
let aptos_types_package = match &self.aptos_module_path {
Some(path) => format!("{}/aptostypes", path),
None => "aptostypes".into(),
};
let mut external_definitions =
crate::common::get_external_definitions(&aptos_types_package);
external_definitions.insert(
"github.com/aptos-labs/serde-reflection/serde-generate/runtime/golang/bcs".to_string(),
Vec::new(),
);
external_definitions.insert("fmt".to_string(), Vec::new());
let (transaction_script_abis, script_fun_abis): (Vec<_>, Vec<_>) = abis
.iter()
.cloned()
.partition(|abi| abi.is_transaction_script_abi());
let mut script_registry: BTreeMap<_, _> = vec![(
"ScriptCall".to_string(),
crate::common::make_abi_enum_container(transaction_script_abis.as_slice()),
)]
.into_iter()
.collect();
let mut script_function_registry: BTreeMap<_, _> = vec![(
"ScriptFunctionCall".to_string(),
crate::common::make_abi_enum_container(script_fun_abis.as_slice()),
)]
.into_iter()
.collect();
script_registry.append(&mut script_function_registry);
let mut comments: BTreeMap<_, _> = abis
.iter()
.map(|abi| {
(
vec![
self.package_name.to_string(),
if abi.is_transaction_script_abi() {
"ScriptCall".to_string()
} else {
"ScriptFunctionCall".to_string()
},
abi.name().to_camel_case(),
],
crate::common::prepare_doc_string(abi.doc()),
)
})
.collect();
comments.insert(
vec![self.package_name.to_string(), "ScriptCall".to_string()],
"Structured representation of a call into a known Move transaction script (legacy)."
.into(),
);
comments.insert(
vec![
self.package_name.to_string(),
"ScriptFunctionCall".to_string(),
],
"Structured representation of a call into a known Move script function.".into(),
);
let config = CodeGeneratorConfig::new(self.package_name.to_string())
.with_comments(comments)
.with_external_definitions(external_definitions)
.with_serialization(false);
let mut generator = golang::CodeGenerator::new(&config);
if let Some(path) = &self.serde_module_path {
generator = generator.with_serde_module_path(path.clone());
}
generator
.output(&mut self.out, &script_registry)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", err)))?;
Ok(())
}
fn output_encode_method(&mut self, abis: &[ScriptABI]) -> Result<()> {
let (transaction_script_abis, script_fun_abis): (Vec<_>, Vec<_>) = abis
.iter()
.cloned()
.partition(|abi| abi.is_transaction_script_abi());
if !transaction_script_abis.is_empty() {
writeln!(
self.out,
r#"
// Build an Aptos `Script` from a structured object `ScriptCall`.
func EncodeScript(call ScriptCall) aptostypes.Script {{"#
)?;
self.out.indent();
writeln!(self.out, "switch call := call.(type) {{")?;
for abi in transaction_script_abis {
if let ScriptABI::TransactionScript(abi) = abi {
let params = std::iter::empty()
.chain(abi.ty_args().iter().map(TypeArgumentABI::name))
.chain(abi.args().iter().map(ArgumentABI::name))
.map(|name| format!("call.{}", name.to_camel_case()))
.collect::<Vec<_>>()
.join(", ");
writeln!(
self.out,
r#"case *ScriptCall__{0}:
return Encode{0}({1})"#,
abi.name().to_camel_case(),
params,
)?;
}
}
writeln!(self.out, "}}")?;
writeln!(self.out, "panic(\"unreachable\")")?;
self.out.unindent();
writeln!(self.out, "}}")?;
}
if !script_fun_abis.is_empty() {
writeln!(
self.out,
r#"
// Build an Aptos `TransactionPayload` from a structured object `ScriptFunctionCall`.
func EncodeScriptFunction(call ScriptFunctionCall) aptostypes.TransactionPayload {{"#
)?;
self.out.indent();
writeln!(self.out, "switch call := call.(type) {{")?;
for abi in script_fun_abis {
if let ScriptABI::ScriptFunction(abi) = abi {
let params = std::iter::empty()
.chain(abi.ty_args().iter().map(TypeArgumentABI::name))
.chain(abi.args().iter().map(ArgumentABI::name))
.map(|name| format!("call.{}", name.to_camel_case()))
.collect::<Vec<_>>()
.join(", ");
writeln!(
self.out,
r#"case *ScriptFunctionCall__{0}{1}:
return Encode{0}{1}({2})"#,
abi.module_name().name().to_string().to_camel_case(),
abi.name().to_camel_case(),
params,
)?;
}
}
writeln!(self.out, "}}")?;
writeln!(self.out, "panic(\"unreachable\")")?;
self.out.unindent();
writeln!(self.out, "}}")?;
}
Ok(())
}
fn output_transaction_script_decode_method(&mut self) -> Result<()> {
writeln!(
self.out,
r#"
// Try to recognize an Aptos `Script` and convert it into a structured object `ScriptCall`.
func DecodeScript(script *aptostypes.Script) (ScriptCall, error) {{
if helper := script_decoder_map[string(script.Code)]; helper != nil {{
val, err := helper(script)
return val, err
}} else {{
return nil, fmt.Errorf("Unknown script bytecode: %s", string(script.Code))
}}
}}"#
)
}
fn output_script_function_decode_method(&mut self) -> Result<()> {
writeln!(
self.out,
r#"
// Try to recognize an Aptos `TransactionPayload` and convert it into a structured object `ScriptFunctionCall`.
func DecodeScriptFunctionPayload(script aptostypes.TransactionPayload) (ScriptFunctionCall, error) {{
switch script := script.(type) {{
case *aptostypes.TransactionPayload__ScriptFunction:
if helper := script_function_decoder_map[string(script.Value.Module.Name) + "_" + string(script.Value.Function)]; helper != nil {{
val, err := helper(script)
return val, err
}} else {{
return nil, fmt.Errorf("Unknown script function: %s::%s", script.Value.Module.Name, script.Value.Function)
}}
default:
return nil, fmt.Errorf("Unknown transaction payload encountered when decoding")
}}
}}"#
)
}
fn output_transaction_script_encoder_function(
&mut self,
abi: &TransactionScriptABI,
) -> Result<()> {
writeln!(
self.out,
"\n{}\nfunc Encode{}({}) aptostypes.Script {{",
Self::quote_doc(abi.doc()),
abi.name().to_camel_case(),
[
Self::quote_type_parameters(abi.ty_args()),
Self::quote_parameters(abi.args()),
]
.concat()
.join(", ")
)?;
self.out.indent();
writeln!(
self.out,
r#"return aptostypes.Script {{
Code: append([]byte(nil), {}_code...),
TyArgs: []aptostypes.TypeTag{{{}}},
Args: []aptostypes.TransactionArgument{{{}}},
}}"#,
abi.name(),
Self::quote_type_arguments(abi.ty_args()),
Self::quote_arguments_for_script(abi.args()),
)?;
self.out.unindent();
writeln!(self.out, "}}")
}
fn output_script_function_encoder_function(&mut self, abi: &ScriptFunctionABI) -> Result<()> {
writeln!(
self.out,
"\n{}\nfunc Encode{}{}({}) aptostypes.TransactionPayload {{",
Self::quote_doc(abi.doc()),
abi.module_name().name().to_string().to_camel_case(),
abi.name().to_camel_case(),
[
Self::quote_type_parameters(abi.ty_args()),
Self::quote_parameters(abi.args()),
]
.concat()
.join(", ")
)?;
self.out.indent();
writeln!(
self.out,
r#"return &aptostypes.TransactionPayload__ScriptFunction {{
aptostypes.ScriptFunction {{
Module: {},
Function: {},
TyArgs: []aptostypes.TypeTag{{{}}},
Args: [][]byte{{{}}},
}},
}}"#,
Self::quote_module_id(abi.module_name()),
Self::quote_identifier(abi.name()),
Self::quote_type_arguments(abi.ty_args()),
Self::quote_arguments(abi.args()),
)?;
self.out.unindent();
writeln!(self.out, "}}")
}
fn output_transaction_script_decoder_function(
&mut self,
abi: &TransactionScriptABI,
) -> Result<()> {
writeln!(
self.out,
"\nfunc decode_{}(script *aptostypes.Script) (ScriptCall, error) {{",
abi.name(),
)?;
self.out.indent();
writeln!(
self.out,
"if len(script.TyArgs) < {0} {{ return nil, fmt.Errorf(\"Was expecting {0} type arguments\") }}",
abi.ty_args().len(),
)?;
writeln!(
self.out,
"if len(script.Args) < {0} {{ return nil, fmt.Errorf(\"Was expecting {0} regular arguments\") }}",
abi.args().len(),
)?;
writeln!(
self.out,
"var call ScriptCall__{0}",
abi.name().to_camel_case(),
)?;
for (index, ty_arg) in abi.ty_args().iter().enumerate() {
writeln!(
self.out,
"call.{} = script.TyArgs[{}]",
ty_arg.name().to_camel_case(),
index,
)?;
}
for (index, arg) in abi.args().iter().enumerate() {
writeln!(
self.out,
r#"if val, err := decode_{}_argument(script.Args[{}]); err == nil {{
call.{} = val
}} else {{
return nil, err
}}
"#,
common::mangle_type(arg.type_tag()),
index,
arg.name().to_camel_case(),
)?;
}
writeln!(self.out, "return &call, nil")?;
self.out.unindent();
writeln!(self.out, "}}")?;
Ok(())
}
fn output_script_function_decoder_function(&mut self, abi: &ScriptFunctionABI) -> Result<()> {
writeln!(
self.out,
"\nfunc decode_{}_{}(script aptostypes.TransactionPayload) (ScriptFunctionCall, error) {{",
abi.module_name().name(),
abi.name(),
)?;
self.out.indent();
writeln!(self.out, "switch script := interface{{}}(script).(type) {{")?;
self.out.indent();
writeln!(
self.out,
"case *aptostypes.TransactionPayload__ScriptFunction:"
)?;
self.out.indent();
writeln!(
self.out,
"if len(script.Value.TyArgs) < {0} {{ return nil, fmt.Errorf(\"Was expecting {0} type arguments\") }}",
abi.ty_args().len(),
)?;
writeln!(
self.out,
"if len(script.Value.Args) < {0} {{ return nil, fmt.Errorf(\"Was expecting {0} regular arguments\") }}",
abi.args().len(),
)?;
writeln!(
self.out,
"var call ScriptFunctionCall__{0}{1}",
abi.module_name().name().to_string().to_camel_case(),
abi.name().to_camel_case(),
)?;
for (index, ty_arg) in abi.ty_args().iter().enumerate() {
writeln!(
self.out,
"call.{} = script.Value.TyArgs[{}]",
ty_arg.name().to_camel_case(),
index,
)?;
}
for (index, arg) in abi.args().iter().enumerate() {
let decoding = match Self::bcs_primitive_type_name(arg.type_tag()) {
None => {
let quoted_type = Self::quote_type(arg.type_tag());
let splits: Vec<_> = quoted_type.rsplitn(2, '.').collect();
let (left, right) = if splits.len() == 2 {
(splits[1], splits[0])
} else {
(splits[0], "")
};
format!(
"{}.BcsDeserialize{}(script.Value.Args[{}])",
left, right, index
)
}
Some(type_name) => format!(
"bcs.NewDeserializer(script.Value.Args[{}]).Deserialize{}()",
index, type_name
),
};
writeln!(
self.out,
r#"
if val, err := {}; err == nil {{
call.{} = val
}} else {{
return nil, err
}}
"#,
decoding,
arg.name().to_camel_case(),
)?;
}
writeln!(self.out, "return &call, nil")?;
self.out.unindent();
writeln!(
self.out,
r#"default:
return nil, fmt.Errorf("Unexpected TransactionPayload encountered when decoding a script function")"#
)?;
self.out.unindent();
writeln!(self.out, "}}")?;
self.out.unindent();
writeln!(self.out, "}}")?;
Ok(())
}
fn output_transaction_script_decoder_map(
&mut self,
abis: &[TransactionScriptABI],
) -> Result<()> {
writeln!(
self.out,
r#"
var script_decoder_map = map[string]func(*aptostypes.Script) (ScriptCall, error) {{"#
)?;
self.out.indent();
for abi in abis {
writeln!(self.out, "string({0}_code): decode_{0},", abi.name(),)?;
}
self.out.unindent();
writeln!(self.out, "}}")
}
fn output_script_function_decoder_map(&mut self, abis: &[ScriptFunctionABI]) -> Result<()> {
writeln!(
self.out,
r#"
var script_function_decoder_map = map[string]func(aptostypes.TransactionPayload) (ScriptFunctionCall, error) {{"#
)?;
self.out.indent();
for abi in abis {
writeln!(
self.out,
"\"{0}_{1}\": decode_{0}_{1},",
abi.module_name().name(),
abi.name(),
)?;
}
self.out.unindent();
writeln!(self.out, "}}")
}
fn output_encoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> {
let required_types = common::get_required_helper_types(abis);
for required_type in required_types {
self.output_encoding_helper(required_type)?;
}
Ok(())
}
fn output_encoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> {
let encoding = match Self::bcs_primitive_type_name(type_tag) {
None => r#"
if val, err := arg.BcsSerialize(); err == nil {{
return val;
}}
"#
.into(),
Some(type_name) => {
format!(
r#"
s := bcs.NewSerializer();
if err := s.Serialize{}(arg); err == nil {{
return s.GetBytes();
}}
"#,
type_name
)
}
};
writeln!(
self.out,
r#"
func encode_{}_argument(arg {}) []byte {{
{}
panic("Unable to serialize argument of type {}");
}}
"#,
common::mangle_type(type_tag),
Self::quote_type(type_tag),
encoding,
common::mangle_type(type_tag)
)
}
fn output_decoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> {
let required_types = common::get_required_helper_types(abis);
for required_type in required_types {
self.output_decoding_helper(required_type)?;
}
Ok(())
}
fn output_decoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> {
use TypeTag::*;
let default_stmt = format!("value = {}(*arg)", Self::quote_type(type_tag));
let (constructor, stmt) = match type_tag {
Bool => ("Bool", default_stmt),
U8 => ("U8", default_stmt),
U64 => ("U64", default_stmt),
U128 => ("U128", default_stmt),
Address => ("Address", "value = arg.Value".into()),
Vector(type_tag) => match type_tag.as_ref() {
U8 => ("U8Vector", default_stmt),
_ => common::type_not_allowed(type_tag),
},
Struct(_) | Signer => common::type_not_allowed(type_tag),
};
writeln!(
self.out,
r#"
func decode_{0}_argument(arg aptostypes.TransactionArgument) (value {1}, err error) {{
if arg, ok := arg.(*aptostypes.TransactionArgument__{2}); ok {{
{3}
}} else {{
err = fmt.Errorf("Was expecting a {2} argument")
}}
return
}}
"#,
common::mangle_type(type_tag),
Self::quote_type(type_tag),
constructor,
stmt,
)
}
fn output_code_constant(&mut self, abi: &ScriptABI) -> Result<()> {
if let ScriptABI::TransactionScript(abi) = abi {
writeln!(
self.out,
"\nvar {}_code = []byte {{{}}};",
abi.name(),
abi.code()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<_>>()
.join(", ")
)?;
}
Ok(())
}
fn quote_identifier(ident: &str) -> String {
format!("\"{}\"", ident)
}
fn quote_address(address: &AccountAddress) -> String {
format!(
"[32]uint8{{ {} }}",
address
.to_vec()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<_>>()
.join(", ")
)
}
fn quote_module_id(module_id: &ModuleId) -> String {
format!(
"aptostypes.ModuleId {{ Address: {}, Name: {} }}",
Self::quote_address(module_id.address()),
Self::quote_identifier(module_id.name().as_str()),
)
}
fn quote_doc(doc: &str) -> String {
let doc = crate::common::prepare_doc_string(doc);
textwrap::indent(&doc, "// ").replace("\n\n", "\n//\n")
}
fn quote_type_parameters(ty_args: &[TypeArgumentABI]) -> Vec<String> {
ty_args
.iter()
.map(|ty_arg| format!("{} aptostypes.TypeTag", ty_arg.name()))
.collect()
}
fn quote_parameters(args: &[ArgumentABI]) -> Vec<String> {
args.iter()
.map(|arg| format!("{} {}", arg.name(), Self::quote_type(arg.type_tag())))
.collect()
}
fn quote_type_arguments(ty_args: &[TypeArgumentABI]) -> String {
ty_args
.iter()
.map(|ty_arg| ty_arg.name().to_string())
.collect::<Vec<_>>()
.join(", ")
}
fn quote_arguments(args: &[ArgumentABI]) -> String {
args.iter()
.map(|arg| Self::quote_transaction_argument(arg.type_tag(), arg.name()))
.collect::<Vec<_>>()
.join(", ")
}
fn quote_arguments_for_script(args: &[ArgumentABI]) -> String {
args.iter()
.map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name()))
.collect::<Vec<_>>()
.join(", ")
}
fn quote_type(type_tag: &TypeTag) -> String {
use TypeTag::*;
match type_tag {
Bool => "bool".into(),
U8 => "uint8".into(),
U64 => "uint64".into(),
U128 => "serde.Uint128".into(),
Address => "aptostypes.AccountAddress".into(),
Vector(type_tag) => match type_tag.as_ref() {
Bool => "[]bool".into(),
U8 => "[]byte".into(),
U64 => "[]uint64".into(),
U128 => "[]uint128".into(),
Address => "[]aptostypes.AccountAddress".into(),
Vector(type_tag) if type_tag.as_ref() == &U8 => "[][]byte".into(),
_ => common::type_not_allowed(type_tag),
},
Struct(_) | Signer => common::type_not_allowed(type_tag),
}
}
fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String {
format!(
"encode_{}_argument({})",
common::mangle_type(type_tag),
name
)
}
fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String {
use TypeTag::*;
match type_tag {
Bool => format!("(*aptostypes.TransactionArgument__Bool)(&{})", name),
U8 => format!("(*aptostypes.TransactionArgument__U8)(&{})", name),
U64 => format!("(*aptostypes.TransactionArgument__U64)(&{})", name),
U128 => format!("(*aptostypes.TransactionArgument__U128)(&{})", name),
Address => format!("&aptostypes.TransactionArgument__Address{{{}}}", name),
Vector(type_tag) => match type_tag.as_ref() {
U8 => format!("(*aptostypes.TransactionArgument__U8Vector)(&{})", name),
_ => common::type_not_allowed(type_tag),
},
Struct(_) | Signer => common::type_not_allowed(type_tag),
}
}
fn bcs_primitive_type_name(type_tag: &TypeTag) -> Option<&'static str> {
use TypeTag::*;
match type_tag {
Bool => Some("Bool"),
U8 => Some("U8"),
U64 => Some("U64"),
U128 => Some("U128"),
Address => None,
Vector(type_tag) => match type_tag.as_ref() {
U8 => Some("Bytes"),
type_tag => Self::bcs_primitive_type_name(type_tag).and(None),
},
Struct(_) | Signer => common::type_not_allowed(type_tag),
}
}
}
pub struct Installer {
install_dir: PathBuf,
serde_module_path: Option<String>,
aptos_module_path: Option<String>,
}
impl Installer {
pub fn new(
install_dir: PathBuf,
serde_module_path: Option<String>,
aptos_module_path: Option<String>,
) -> Self {
Installer {
install_dir,
serde_module_path,
aptos_module_path,
}
}
}
impl crate::SourceInstaller for Installer {
type Error = Box<dyn std::error::Error>;
fn install_transaction_builders(
&self,
name: &str,
abis: &[ScriptABI],
) -> std::result::Result<(), Self::Error> {
let dir_path = self.install_dir.join(name);
std::fs::create_dir_all(&dir_path)?;
let mut file = std::fs::File::create(dir_path.join("lib.go"))?;
output(
&mut file,
self.serde_module_path.clone(),
self.aptos_module_path.clone(),
name.to_string(),
abis,
)?;
Ok(())
}
}