use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::{near, AccountId, CryptoHash}; use std::io::{Error, ErrorKind, Read, Write}; pub type BlockHeight = u64; pub type Nonce = u64; pub type Balance = u128; pub type Gas = u64; const SECP256K1_SIGNATURE_LENGTH: usize = 65; #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct Secp256K1Signature([u8; SECP256K1_SIGNATURE_LENGTH]); #[derive(Clone)] pub enum Signature { ED25519(ed25519_dalek::Signature), SECP256K1(Secp256K1Signature), } impl BorshSerialize for Signature { fn serialize(&self, writer: &mut W) -> Result<(), Error> { match self { Signature::ED25519(signature) => { BorshSerialize::serialize(&0u8, writer)?; writer.write_all(&signature.to_bytes())?; } Signature::SECP256K1(signature) => { BorshSerialize::serialize(&1u8, writer)?; writer.write_all(&signature.0)?; } } Ok(()) } } impl BorshDeserialize for Signature { fn deserialize_reader(rd: &mut R) -> std::io::Result { let key_type = KeyType::try_from(u8::deserialize_reader(rd)?) .map_err(|err| Error::new(ErrorKind::InvalidData, err.to_string()))?; match key_type { KeyType::ED25519 => { let array: [u8; ed25519_dalek::SIGNATURE_LENGTH] = BorshDeserialize::deserialize_reader(rd)?; // Sanity-check that was performed by ed25519-dalek in from_bytes before version 2, // but was removed with version 2. It is not actually any good a check, but we have // it here in case we need to keep backward compatibility. Maybe this check is not // actually required, but please think carefully before removing it. if array[ed25519_dalek::SIGNATURE_LENGTH - 1] & 0b1110_0000 != 0 { return Err(Error::new(ErrorKind::InvalidData, "signature error")); } Ok(Signature::ED25519(ed25519_dalek::Signature::from_bytes( &array, ))) } KeyType::SECP256K1 => { let array: [u8; 65] = BorshDeserialize::deserialize_reader(rd)?; Ok(Signature::SECP256K1(Secp256K1Signature(array))) } } } } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct Secp256K1PublicKey([u8; 64]); #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct ED25519PublicKey(pub [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]); #[derive(Clone)] pub enum PublicKey { /// 256 bit elliptic curve based public-key. ED25519(ED25519PublicKey), /// 512 bit elliptic curve based public-key used in Bitcoin's public-key cryptography. SECP256K1(Secp256K1PublicKey), } impl BorshSerialize for PublicKey { fn serialize(&self, writer: &mut W) -> Result<(), Error> { match self { PublicKey::ED25519(public_key) => { BorshSerialize::serialize(&0u8, writer)?; writer.write_all(&public_key.0)?; } PublicKey::SECP256K1(public_key) => { BorshSerialize::serialize(&1u8, writer)?; writer.write_all(&public_key.0)?; } } Ok(()) } } pub enum KeyType { ED25519 = 0, SECP256K1 = 1, } #[derive(Debug, thiserror::Error)] pub enum ParseKeyTypeError { #[error("unknown key type '{unknown_key_type}'")] UnknownKeyType { unknown_key_type: String }, } impl TryFrom for KeyType { type Error = ParseKeyTypeError; fn try_from(value: u8) -> Result { match value { 0 => Ok(KeyType::ED25519), 1 => Ok(KeyType::SECP256K1), unknown_key_type => Err(Self::Error::UnknownKeyType { unknown_key_type: unknown_key_type.to_string(), }), } } } impl BorshDeserialize for PublicKey { fn deserialize_reader(rd: &mut R) -> std::io::Result { let key_type = KeyType::try_from(u8::deserialize_reader(rd)?) .map_err(|err| Error::new(ErrorKind::InvalidData, err.to_string()))?; match key_type { KeyType::ED25519 => Ok(PublicKey::ED25519(ED25519PublicKey( BorshDeserialize::deserialize_reader(rd)?, ))), KeyType::SECP256K1 => Ok(PublicKey::SECP256K1(Secp256K1PublicKey( BorshDeserialize::deserialize_reader(rd)?, ))), } } } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct FunctionCallPermission { /// Allowance is a balance limit to use by this access key to pay for function call gas and /// transaction fees. When this access key is used, both account balance and the allowance is /// decreased by the same value. /// `None` means unlimited allowance. /// NOTE: To change or increase the allowance, the old access key needs to be deleted and a new /// access key should be created. pub allowance: Option, // This isn't an AccountId because already existing records in testnet genesis have invalid // values for this field (see: https://github.com/near/nearcore/pull/4621#issuecomment-892099860) // we accommodate those by using a string, allowing us to read and parse genesis. /// The access key only allows transactions with the given receiver's account id. pub receiver_id: String, /// A list of method names that can be used. The access key only allows transactions with the /// function call of one of the given method names. /// Empty list means any method name can be used. pub method_names: Vec, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub enum AccessKeyPermission { FunctionCall(FunctionCallPermission), /// Grants full access to the account. /// NOTE: It's used to replace account-level public keys. FullAccess, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct AccessKey { /// Nonce for this access key, used for tx nonce generation. When access key is created, nonce /// is set to `(block_height - 1) * 1e6` to avoid tx hash collision on access key re-creation. /// See for more details. pub nonce: Nonce, /// Defines permissions for this access key. pub permission: AccessKeyPermission, } /// An action that adds key with public key associated #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct AddKeyAction { /// A public key which will be associated with an access_key pub public_key: PublicKey, /// An access key with the permission pub access_key: AccessKey, } #[derive(BorshDeserialize, BorshSerialize, Clone)] /// Create account action pub struct CreateAccountAction {} #[derive(Clone)] #[near(serializers = [borsh])] pub struct DeleteAccountAction { pub beneficiary_id: AccountId, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct DeleteKeyAction { /// A public key associated with the access_key to be deleted. pub public_key: PublicKey, } #[derive(BorshDeserialize, BorshSerialize, Clone)] /// Deploy contract action pub struct DeployContractAction { /// WebAssembly binary pub code: Vec, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct TransferAction { pub deposit: Balance, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct FunctionCallAction { pub method_name: String, pub args: Vec, pub gas: Gas, pub deposit: Balance, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub enum GlobalContractDeployMode { /// Contract is deployed under its code hash. /// Users will be able reference it by that hash. /// This effectively makes the contract immutable. CodeHash, /// Contract is deployed under the owner account id. /// Users will be able reference it by that account id. /// This allows the owner to update the contract for all its users. AccountId, } #[derive(BorshDeserialize, BorshSerialize, Clone)] #[borsh(use_discriminant = false)] #[repr(u8)] pub enum GlobalContractIdentifier { CodeHash(CryptoHash) = 0, AccountId(AccountId) = 1, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct DeployGlobalContractAction { /// WebAssembly binary pub code: Vec, pub deploy_mode: GlobalContractDeployMode, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct UseGlobalContractAction { pub contract_identifier: GlobalContractIdentifier, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct StakeAction { /// Amount of tokens to stake. pub stake: Balance, /// Validator key which will be used to sign transactions on behalf of signer_id pub public_key: PublicKey, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct NonDelegateAction(Action); #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct DelegateAction { /// Signer of the delegated actions pub sender_id: AccountId, /// Receiver of the delegated actions. pub receiver_id: AccountId, /// List of actions to be executed. /// /// With the meta transactions MVP defined in NEP-366, nested /// DelegateActions are not allowed. A separate type is used to enforce it. pub actions: Vec, /// Nonce to ensure that the same delegate action is not sent twice by a /// relayer and should match for given account's `public_key`. /// After this action is processed it will increment. pub nonce: Nonce, /// The maximal height of the block in the blockchain below which the given DelegateAction is valid. pub max_block_height: BlockHeight, /// Public key used to sign this delegated action. pub public_key: PublicKey, } #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct SignedDelegateAction { pub delegate_action: DelegateAction, pub signature: Signature, } #[derive(BorshDeserialize, BorshSerialize, Clone)] #[borsh(use_discriminant = false)] #[repr(u8)] pub enum Action { /// Create an (sub)account using a transaction `receiver_id` as an ID for /// a new account ID must pass validation rules described here /// . CreateAccount(CreateAccountAction) = 0, /// Sets a Wasm code to a receiver_id DeployContract(DeployContractAction) = 1, FunctionCall(Box) = 2, Transfer(TransferAction) = 3, Stake(Box) = 4, AddKey(Box) = 5, DeleteKey(Box) = 6, DeleteAccount(DeleteAccountAction) = 7, Delegate(Box) = 8, DeployGlobalContract(DeployGlobalContractAction) = 9, UseGlobalContract(Box) = 10, } #[derive(BorshDeserialize, BorshSerialize)] pub struct TransactionV0 { /// An account on which behalf transaction is signed pub signer_id: AccountId, /// A public key of the access key which was used to sign an account. /// Access key holds permissions for calling certain kinds of actions. pub public_key: PublicKey, /// Nonce is used to determine order of transaction in the pool. /// It increments for a combination of `signer_id` and `public_key` pub nonce: Nonce, /// Receiver account for this transaction pub receiver_id: AccountId, /// The hash of the block in the blockchain on top of which the given transaction is valid pub block_hash: CryptoHash, /// A list of actions to be applied pub actions: Vec, } #[derive(BorshDeserialize, BorshSerialize)] pub struct TransactionV1 { /// An account on which behalf transaction is signed pub signer_id: AccountId, /// A public key of the access key which was used to sign an account. /// Access key holds permissions for calling certain kinds of actions. pub public_key: PublicKey, /// Nonce is used to determine order of transaction in the pool. /// It increments for a combination of `signer_id` and `public_key` pub nonce: Nonce, /// Receiver account for this transaction pub receiver_id: AccountId, /// The hash of the block in the blockchain on top of which the given transaction is valid pub block_hash: CryptoHash, /// A list of actions to be applied pub actions: Vec, /// Priority fee. Unit is 10^12 yoctoNEAR pub priority_fee: u64, } pub enum Transaction { V0(TransactionV0), V1(TransactionV1), } impl Transaction { // pub fn signer_id(&self) -> &AccountId { // match self { // Transaction::V0(tx) => &tx.signer_id, // Transaction::V1(tx) => &tx.signer_id, // } // } pub fn receiver_id(&self) -> &AccountId { match self { Transaction::V0(tx) => &tx.receiver_id, Transaction::V1(tx) => &tx.receiver_id, } } // pub fn public_key(&self) -> &PublicKey { // match self { // Transaction::V0(tx) => &tx.public_key, // Transaction::V1(tx) => &tx.public_key, // } // } // pub fn nonce(&self) -> Nonce { // match self { // Transaction::V0(tx) => tx.nonce, // Transaction::V1(tx) => tx.nonce, // } // } pub fn actions(&self) -> &[Action] { match self { Transaction::V0(tx) => &tx.actions, Transaction::V1(tx) => &tx.actions, } } // pub fn take_actions(self) -> Vec { // match self { // Transaction::V0(tx) => tx.actions, // Transaction::V1(tx) => tx.actions, // } // } // pub fn block_hash(&self) -> &CryptoHash { // match self { // Transaction::V0(tx) => &tx.block_hash, // Transaction::V1(tx) => &tx.block_hash, // } // } // pub fn priority_fee(&self) -> Option { // match self { // Transaction::V0(_) => None, // Transaction::V1(tx) => Some(tx.priority_fee), // } // } } impl BorshDeserialize for Transaction { /// Deserialize based on the first and second bytes of the stream. For V0, we do backward compatible deserialization by deserializing /// the entire stream into V0. For V1, we consume the first byte and then deserialize the rest. fn deserialize_reader(reader: &mut R) -> std::io::Result { let u1 = u8::deserialize_reader(reader)?; let u2 = u8::deserialize_reader(reader)?; let u3 = u8::deserialize_reader(reader)?; let u4 = u8::deserialize_reader(reader)?; // This is a ridiculous hackery: because the first field in `TransactionV0` is an `AccountId` // and an account id is at most 64 bytes, for all valid `TransactionV0` the second byte must be 0 // because of the little endian encoding of the length of the account id. // On the other hand, for `TransactionV1`, since the first byte is 1 and an account id must have nonzero // length, so the second byte must not be zero. Therefore, we can distinguish between the two versions // by looking at the second byte. let read_signer_id = |buf: [u8; 4], reader: &mut R| -> std::io::Result { let str_len = u32::from_le_bytes(buf); let mut str_vec = Vec::with_capacity(str_len as usize); for _ in 0..str_len { str_vec.push(u8::deserialize_reader(reader)?); } AccountId::try_from(String::from_utf8(str_vec).map_err(|_| { Error::new( ErrorKind::InvalidData, "Failed to parse AccountId from bytes", ) })?) .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string())) }; if u2 == 0 { let signer_id = read_signer_id([u1, u2, u3, u4], reader)?; let public_key = PublicKey::deserialize_reader(reader)?; let nonce = Nonce::deserialize_reader(reader)?; let receiver_id = AccountId::deserialize_reader(reader)?; let block_hash = CryptoHash::deserialize_reader(reader)?; let actions = Vec::::deserialize_reader(reader)?; Ok(Transaction::V0(TransactionV0 { signer_id, public_key, nonce, receiver_id, block_hash, actions, })) } else { let u5 = u8::deserialize_reader(reader)?; let signer_id = read_signer_id([u2, u3, u4, u5], reader)?; let public_key = PublicKey::deserialize_reader(reader)?; let nonce = Nonce::deserialize_reader(reader)?; let receiver_id = AccountId::deserialize_reader(reader)?; let block_hash = CryptoHash::deserialize_reader(reader)?; let actions = Vec::::deserialize_reader(reader)?; let priority_fee = u64::deserialize_reader(reader)?; Ok(Transaction::V1(TransactionV1 { signer_id, public_key, nonce, receiver_id, block_hash, actions, priority_fee, })) } } }