如果你想学习如何开发Solana智能合约和项目,那么你来对地方了。

Solana是一个新兴的、高性能的且无许可的公链,它提供了快速、经济且可扩展的交易体验,并且它还支持使用Rust,C++和C语言来编写智能合约。

在本技术教程中,我们将一起看一看如何在Solana Devnet(测试网)编写、部署合约并与之进行交互,以及如何在Solana智能合约中使用Chainlink Price Feeds。

如何开发并部署Solana智能合约,完整视频:https://v.qq.com/x/page/k3303htl9gc.html

Solana的架构和编程模型

Solana是一个高性能的区块链,每秒能够处理数千笔交易,并且出块时间达到了亚秒级。它通过拜占庭容错(BFT)共识机制实现了这一点,该机制利用了一种称之为历史证明(PoH)的创新密码学函数。

历史证明

历史证明 (PoH) 通过使用高频可验证延迟函数(VDF),随着时间的推移建立可通过密码验证的事件顺序(在本例中为交易)。 从本质上讲,这意味着 PoH 就像一个加密时钟,可以帮助网络就时间和事件顺序达成一致,而无需等待其他节点的消息。这就像古老的水钟可以通过观察水位上升来记录时间的流逝一样,历史证明的区块链状态哈希的连续输出能够给出可验证的事件顺序。

这允许并行处理有序事件来帮助提升网络的性能,而在传统的区块链场景中,单个进程验证并打包要包含在下一个块中的所有交易。

一个简单的类比是想象一个100块的大型拼图。在正常情况下,完成拼图需要一个或多个人一定的时间。但是想象一下,如果事先所有拼图块都印有与其位置相对应的数字,从拼图的左上角到右下角,并按顺序排列成一行。 因为拼图的确切顺序和它们在拼图中的位置是事先知道的,所以可以让多人专注于每个部分,可以更快地解决拼图。 这是相对时间而言具有可验证的事件序列对共识机制的影响;它使得将事务分解为多个并行进程成为可能。

智能合约架构

Solana提供了一种不同于传统的基于EVM的区块链的智能合约模型。在传统的基于EVM的链中,合约代码/逻辑和状态被组合成一个部署在链上的合约。Solana中智能合约(或程序)是只读或无状态的,并且只包含程序逻辑。一旦部署后,智能合约就可以通过外部账户进行交互。Solana中与程序交互的账户会存储与程序交互相关的数据。这创建了状态(帐户)和合约逻辑(程序)的逻辑分离。这是Solana和基于EVM的智能合约之间的关键区别。以太坊上的账户与Solana上的账户不同,Solana账户可以存储数据(包括钱包信息),而以太坊账户并不存储数据。

除此之外,Solana还提供了CLI(命令行) 和JSON RPC API,这些可以用于去中心化应用程序与Solana区块链进行交互。还可以使用现有的SDK,用于客户端与区块链和Solana程序对话。

Solana开发工作流的抽象表述。 来源:Solana 文档

部署第一个Solana智能合约

在本节,你将会创建并部署一个“hello world”的Solana程序,使用Rust进行编写。

要求

在继续之前,应安装以下工具:
• NodeJS v14或更高版本 & NPM
• 最新的稳定版本的Rust
• Solana CLI v1.7.11或者更高版本
• Git

HelloWorld程序

该HelloWorld程序是一个智能合约,它可将输出打印到控制台,并统计给定的帐户调用该程序的次数,并将该数字存储在链上。我们将代码分解成不同的部分来讲解。

第一部分定义了一些标准的Solana程序参数并定义了程序的入口(“process_instruction”函数)。 此外,它还使用borsh对传入和传出部署程序的参数进行序列化和反序列化。

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{account_info::{next_account_info, AccountInfo},entrypoint,entrypoint::ProgramResult,msg,program_error::ProgramError,pubkey::Pubkey,
};/// Define the type of state stored in accounts
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingAccount {/// number of greetingspub counter: u32,
}// Declare and export the program's entrypoint
entrypoint!(process_instruction);

process_instruction函数接受program_id,这是一个公钥,也即程序部署后的地址;以及accountInfo,这是用于和合约交互的账户。

pub fn process_instruction(program_id: &Pubkey, // Public key of the account the hello world program was loaded intoaccounts: &[AccountInfo], // The account to say hello to_instruction_data: &[u8], // Ignored, all helloworld instructions are hellos

ProgramResult是写程序的主要逻辑的地方。在本教程中,它只是打印一条消息,然后遍历“accounts”来选择帐户。 但是,在我们的示例中,将只有一个帐户。

) -> ProgramResult {msg!("Hello World Rust program entrypoint");// Iterating accounts is safer then indexinglet accounts_iter = &mut accounts.iter();// Get the account to say hello tolet account = next_account_info(accounts_iter)?;

接下来,程序会检查该帐户是否有权修改指定帐户的数据。

// The account must be owned by the program in order to modify its dataif account.owner != program_id {msg!("Greeted account does not have the correct program id");return Err(ProgramError::IncorrectProgramId);}

最后,该函数会将现有帐户存储的数字加一并将结果写回,然后显示一条消息。

// Increment and store the number of times the account has been greetedlet mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;greeting_account.counter += 1;greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;msg!("Greeted {} time(s)!", greeting_account.counter);Ok(())

部署程序

第一步是复制代码。

git clone https://github.com/solana-labs/example-helloworld
cd example-helloworld

完成后,可以设置当前的环境为devnet。这是为Solana开发者们准备的编写和测试合约的网络的测试网。

solana config set --url https://api.devnet.solana.com

接下来,需要为账户创建一个密钥对。这对于在Solana测试网上部署合约来说是必要的。注意:这种存储密钥对的方法不安全,应该仅用于demo目的。为了安全,系统将提示你输入密码。

solana-keygen new --force

现在已经创建了一个帐户,可以使用空投程序来获取一些SOL通证。需要一些lamports(部分SOL通证)来部智能合约。 该命令请求获取SOL通证到你新生成的帐户中:

solana airdrop 5

现在已准备好构建hello world程序。 可以通过运行以下命令来构建它:

npm run build:program-rust


构建程序

程序构建完成后,就可以将其部署到devnet上。上一个命令的输出将为你提供接下来需要运行的命令,但它应该类似于下面这种:

solana program deploy dist/program/helloworld.so

最终结果是成功将hello world程序部署到devnet上,并且有一个指定的Program Id。这可以在Solana Devnet浏览器上进行检查。


部署程序

在Devnet浏览器上检查部署的程序

与部署的程序交互

为了与部署好的程序交互,hello-world代码库提供了一个简单的客户端。这个客户端是用Typescript编写的,使用了Solana的web3.js库和Solana web3 API。

客户端

客户端入口是main.ts文件,它按特定顺序执行任务,其中大部分任务都包含在hello_world.ts文件中。

首先,客户端会建立一个和节点的连接,使用的的函数是establishConnection:

export async function establishConnection(): Promise {const rpcUrl = await getRpcUrl();connection = new Connection(rpcUrl, 'confirmed');const version = await connection.getVersion();console.log('Connection to cluster established:', rpcUrl, version);
}

然后会调用establishPayer函数来确保有一个能支付的账户,如果没有的话就按需创建一个新的。

export async function establishPayer(): Promise {let fees = 0;if (!payer) {const {feeCalculator} = await connection.getRecentBlockhash();// Calculate the cost to fund the greeter accountfees += await connection.getMinimumBalanceForRentExemption(GREETING_SIZE);// Calculate the cost of sending transactionsfees += feeCalculator.lamportsPerSignature * 100; // wagpayer = await getPayer();}

客户端然后会调用“checkProgram”函数,该函数从 ./dist/program/helloworld-keypair.json中加载部署程序的密钥对,并使用密钥对的公钥来获取程序帐户。如果程序不存在,客户端会报错并停止。如果该程序确实存在,它将创建一个新帐户存储程序状态,并将程序指定为其所有者,在这种情况下,它是指程序已执行的次数。

export async function checkProgram(): Promise {// Read program id from keypair filetry {const programKeypair = await createKeypairFromFile(PROGRAM_KEYPAIR_PATH);programId = programKeypair.publicKey;} catch (err) {const errMsg = (err as Error).message;throw new Error(`Failed to read program keypair at '${PROGRAM_KEYPAIR_PATH}' due to error: ${errMsg}. Program may need to be deployed with \`solana program deploy dist/program/helloworld.so\``,);}// Check if the program has been deployedconst programInfo = await connection.getAccountInfo(programId);if (programInfo === null) {if (fs.existsSync(PROGRAM_SO_PATH)) {throw new Error('Program needs to be deployed with `solana program deploy dist/program/helloworld.so`',);} else {throw new Error('Program needs to be built and deployed');}} else if (!programInfo.executable) {throw new Error(`Program is not executable`);}console.log(`Using program ${programId.toBase58()}`);// Derive the address (public key) of a greeting account from the program so that it's easy to find later.const GREETING_SEED = 'hello';greetedPubkey = await PublicKey.createWithSeed(payer.publicKey,GREETING_SEED,programId,);

然后客户端调用“sayHello”函数并向程序发送“hello”交易事务。该交易包含一条指令,该指令包含要调用的helloworld程序帐户的公钥以及客户端希望与其交互的帐户。每次客户端对一个帐户执行此交易时,程序都会增加目标帐户数据存储中的计数。

export async function sayHello(): Promise {console.log('Saying hello to', greetedPubkey.toBase58());const instruction = new TransactionInstruction({keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],programId,data: Buffer.alloc(0), // All instructions are hellos});await sendAndConfirmTransaction(connection,new Transaction().add(instruction),[payer],);
}

最后,客户端调用“reportGreetings”查询账户数据以检索账户当前调用sayHello事务的次数。

export async function reportGreetings(): Promise {const accountInfo = await connection.getAccountInfo(greetedPubkey);if (accountInfo === null) {throw 'Error: cannot find the greeted account';}const greeting = borsh.deserialize(GreetingSchema,GreetingAccount,accountInfo.data,);console.log(greetedPubkey.toBase58(),'has been greeted',greeting.counter,'time(s)',);

运行客户端

在运行客户端从部署的程序中读取数据之前,还需要安装客户端的依赖项。

npm install

这步完成后,可以开启客户端。

npm run start

可以从输出中看到程序成功执行,并且会展示账户已经打招呼的次数。再运行一次会增加该数值。


开启Hello World客户端和部署的程序交互

恭喜!你已经成功在Solana测试网上部署了程序并与之交互了。现在我们进入另一个Solana的程序和客户端案例,但这次我们来使用Chainlink的Price Feeds。

Solana上使用Chainlink Price Feeds

Solana上的DeFi应用生态正在加速增长。为了支持基本的DeFi机制并执行关键的链上功能,例如以公平市场价格发放贷款或清算抵押不足的头寸,这些dApp都需要访问高度可靠和高质量的市场数据。

Solana最近在他们的devnet网络上集成了Chainlink Price Feeds,为开发者提供高度去中心化、高质量、亚秒级更新的价格参考数据,用于构建混合型智能合约。

结合Solana每秒支持多达 65,000 笔交易的能力及其极低的交易费用,Chainlink Price Feeds有可能增强DeFi协议基础设施,使其能够在交易执行和风险管理质量方面与传统金融系统竞争。

在接下来的代码案例中,我们将在Solana测试网上部署合约并与其交互。

要求

• NodeJS v14或更高版本 & NPM
• 最新的稳定版本的Rust
• Solana CLI v1.7.11或更高版本
• Git

Chainlink Solana Demo程序

Chainlink Solana Demo程序是一个智能合约,只需连接到devnet上的Chainlink Price Feed帐户,即可获取并存储指定价格对的最新价格。

Solana上的Chainlink Price Feeds都使用同一个程序,每个单独的Price Feed都有一个单独的帐户,该帐户使用该程序提交价格更新。

第一部分包括使用borsh对传入和传出部署程序的参数进行序列化和反序列化的正常包含和声明。 除此之外,还设置了一些结构体来存储Price Feeds结果和十进制数据。

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{account_info::{next_account_info, AccountInfo},entrypoint,entrypoint::ProgramResult,msg,pubkey::Pubkey,
};struct Decimal {pub value: u128,pub decimals: u32,
}/// Define the type of state stored in accounts
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct PriceFeedAccount {/// number of greetingspub answer: u128,
}impl Decimal {pub fn new(value: u128, decimals: u32) -> Self {Decimal { value, decimals }}
}

接下来,程序将入口声明为“process_instructions”函数。

// Declare and export the program's entrypoint
entrypoint!(process_instruction);

最后,“process_instruction”函数是包含智能合约主要逻辑的程序部分。它接收具有指定Price Feed地址的交易,并处理在链上查询和存储价格数据的步骤。该函数还从chainlink-solana包中调用get_price()函数 ,该包需要通过Cargo.toml文件从GitHub导入。该函数使用指定的Price Feed的地址获取价格并存储。

pub fn process_instruction(_program_id: &Pubkey, // Ignoredaccounts: &[AccountInfo], // Public key of the account to read price data from_instruction_data: &[u8], // Ignored
) -> ProgramResult {msg!("Chainlink Solana Demo program entrypoint");let accounts_iter = &mut accounts.iter();// This is the account of our our accountlet my_account = next_account_info(accounts_iter)?;// This is the account of the price feed datalet feed_account = next_account_info(accounts_iter)?;const DECIMALS: u32 = 9;let price = chainlink::get_price(&chainlink::id(), feed_account)?;if let Some(price) = price {let decimal = Decimal::new(price, DECIMALS);msg!("Price is {}", decimal);} else {msg!("No current price");}// Store the price ourselveslet mut price_data_account = PriceFeedAccount::try_from_slice(&my_account.data.borrow())?;price_data_account.answer = price.unwrap_or(0);price_data_account.serialize(&mut &mut my_account.data.borrow_mut()[..])?;

部署程序

第一步是复制代码。

git clone https://github.com/smartcontractkit/chainlink-solana-demo
cd chainlink-solana-demo

完成后,可以设置当前的环境为devnet。这是为Solana开发者们准备的编写和测试合约的网络的测试网。

solana config set --url https://api.devnet.solana.com

接下来,需要为账户创建一个密钥对。这对于在Solana测试网上部署合约来说是必要的。注意:这种存储密钥对的方法不安全,应该仅用于demo目的。为了安全,系统将提示你输入密码。

mkdir solana-wallet
solana-keygen new --outfile solana-wallet/keypair.json

现在已经创建了一个帐户,可以使用空投程序来获取一些SOL通证。需要一些lamports(部分SOL通证)来部智能合约。 该命令请求获取SOL通证到你新生成的帐户中。

solana airdrop 5 $(solana-keygen pubkey solana-wallet/keypair.json)

如果这步不行的话,可以通过下面的指令查看公钥,并使用该地址从solfaucet水龙头中请求一些SOL通证。

solana-keygen pubkey ./solana-wallet/keypair.json

现在就可以使用Solana BPF构建Chainlink Solana演示程序了。

cargo build-bpf

程序构建完成后,就可以将其部署到devnet上。上一个命令的输出将为你提供接下来需要运行的命令,但它应该类似于下面这种。注意在命令后面添加的–keypair选项。

solana program deploy target/deploy/chainlink_solana_demo.so --keypair solana-wallet/keypair.json


编译Chainlink Solana Demo

最终结果是成功将此程序部署到devnet上,并且有一个指定的Program Id。这可以在Solana Devnet浏览器上进行检查。


部署Chainlink Solana Demo


在Devnet浏览器查看部署完成的Chainlink Solana Demo

与部署后的程序交互

为了与部署好的程序交互,hello-world代码库提供了一个简单的客户端。这个客户端是用Typescript编写的,使用了Solana的web3.js库和Solana web3 API。

客户端

客户端入口是main.ts文件,它按特定顺序执行任务,其中大部分任务都包含在hello_world.ts文件中。

首先,客户端会建立一个和节点的连接,使用的的函数是establishConnection。

export async function establishConnection(): Promise {const rpcUrl = await getRpcUrl()connection = new Connection(rpcUrl, 'confirmed')const version = await connection.getVersion()console.log('Connection to cluster established:', rpcUrl, version)
}

然后会调用establishPayer函数来确保有一个能支付的账户,如果没有的话就按需创建一个新的。

export async function establishPayer(): Promise {let fees = 0if (!payer) {const { feeCalculator } = await connection.getRecentBlockhash()// Calculate the cost to fund the greeter accountfees += await connection.getMinimumBalanceForRentExemption(AGGREGATOR_SIZE)// Calculate the cost of sending transactionsfees += feeCalculator.lamportsPerSignature * 100 // wagtry {// Get payer from cli configpayer = await getPayer()} catch (err) {// Fund a new payer via airdroppayer = await newAccountWithLamports(connection, fees)}}

客户端然后会调用“checkProgram”函数,该函数从 ./dist/program/helloworld-keypair.json中加载部署程序的密钥对,并使用密钥对的公钥来获取程序帐户。如果程序不存在,客户端会报错并停止。如果该程序确实存在,它将创建一个新帐户存储程序状态,并将程序指定为其所有者。

export async function checkProgram(): Promise {// Read program id from keypair filetry {const programKeypair = await createKeypairFromFile(PROGRAM_KEYPAIR_PATH);programId = programKeypair.publicKey;} catch (err) {const errMsg = (err as Error).message;throw new Error(`Failed to read program keypair at '${PROGRAM_KEYPAIR_PATH}' due to error: ${errMsg}. Program may need to be deployed with \`solana program deploy dist/program/helloworld.so\``,);}// Check if the program has been deployedconst programInfo = await connection.getAccountInfo(programId);if (programInfo === null) {if (fs.existsSync(PROGRAM_SO_PATH)) {throw new Error('Program needs to be deployed with `solana program deploy dist/program/chainlink_solana_demo.so`',);} else {throw new Error('Program needs to be built and deployed');}} else if (!programInfo.executable) {throw new Error(`Program is not executable`);}console.log(`Using program ${programId.toBase58()}`);// Derive the address (public key) of a greeting account from the program so that it's easy to find later.const GREETING_SEED = 'hello';greetedPubkey = await PublicKey.createWithSeed(payer.publicKey,GREETING_SEED,programId,);

然后,客户端调用“getPrice”函数并向程序发送交易。交易包含一个Price Feed帐户作为参数。在本例中,发送的是Chainlink Solana Devnet Price Feeds页面中列出的SOL/USD Feed帐户。指令发送完成后,它就会等待交易确认。

export async function getPrice(): Promise {console.log('Getting data from ', readingPubkey.toBase58())const priceFeedAccount = "FmAmfoyPXiA8Vhhe6MZTr3U6rZfEZ1ctEHay1ysqCqcf"const AggregatorPublicKey = new PublicKey(priceFeedAccount)const instruction = new TransactionInstruction({keys: [{ pubkey: readingPubkey, isSigner: false, isWritable: true },{ pubkey: AggregatorPublicKey, isSigner: false, isWritable: false }],programId,data: Buffer.alloc(0), // All instructions are hellos})await sendAndConfirmTransaction(connection,new Transaction().add(instruction),[payer],)
}

最后,客户端会查询账户数据以检索出SOL/USD Price Feed存储的价格数据。

export async function reportPrice(): Promise {const accountInfo = await connection.getAccountInfo(readingPubkey)if (accountInfo === null) {throw new Error('Error: cannot find the aggregator account')}const latestPrice = borsh.deserialize(AggregatorSchema,AggregatorAccount,accountInfo.data,)console.log("Current price of SOL/USD is: ", latestPrice.answer.toString())
}

运行客户端

在运行客户端从部署的程序中读取数据之前,还需要安装客户端的依赖项。

cd client
yarn

这步完成后,可以开启客户端。

yarn start

可以从输出中看到程序成功执行,并且它应该显示存储的当前SOL/USD价格数据。

运行Chainlink Solana demo客户端并与部署的程序交

总结

Solana为构建智能合约和去中心化应用程序提供了高速、低成本、可扩展的区块链。使用Solana智能合约和Chainlink Price Feeds,开发者们能够利用Chainlink Price Feeds 提供的高质量数据和Solana区块链上可用的亚秒级更新等优势,创建快速、可扩展的DeFi应用程序。
探索更多Chainlink技术教程,可以查看官方Chainlink YouTube上工程相关的视频教程播放列表,并访问 docs.chain.link上的 Chainlink文档。如果要讨论集成相关,可以点击此处联系专家。

如何开发并部署Solana智能合约相关推荐

  1. ETH 开发环境搭建及智能合约 helloworld

    ETH 开发环境搭建及智能合约 helloworld 自己的菜鸟级的起步教程,给自己长记性 准备 什么是以太坊 以太坊白皮书 以太坊白皮书_ZH 以太坊白皮书_EN 环境介绍 这里使用了,以下两个开发 ...

  2. 开发一个简单的智能合约

    一.环境搭建 搭建Truffle框架 简介:这是一个流行的以太坊开发框架,内置了智能合约编译,连接,部署等功能 Truffle框架依赖Node,需要使用npm来安装,首先需要安装node,npm会同时 ...

  3. 我自己可以挖矿了!使用Ethereum C++客户端Aleth建一个私有网络,并使用Remix部署一个智能合约

    本文是按照这个教程执行的结果记录:Creating a private network and deploying a contract with Remix Ethereum Aleth在Wins上 ...

  4. 以太坊智能合约Java_以太坊:调用已部署的智能合约

    目录 1. 调用已部署的智能合约 相关截图来自: 1.1. ABI是payload编码的依据 ABI,应用二进制接口(Application Binary Interface).它是从区块链外部与合约 ...

  5. 《如何五分钟创建自己的新币token 》Dapp开发 Web3+以太坊+智能合约开发 (一)

    Dapp开发 Web3+以太坊+智能合约开发 (一)如何创建自己的新币token 前言 就是想开发一个Dapp,实现一下功能.未来有可能的话建立一下自己的社区.话不多说直接开始: 开发自己的ETH代币 ...

  6. 以太坊Dapp通过web3js部署调用智能合约

    通过web3js部署调用智能合约 1.在线编译智能合约 2.部署合约 3.调用合约 参考视频:https://www.bilibili.com/video/BV14z4y1Z7Jd?p=1 1.在线编 ...

  7. 在geth客户端调用已部署的智能合约

    什么是合约? 合约是代码(它的功能)和数据(它的状态)的集合,存在于以太坊区块链的特定地址. 合约账户能够在彼此之间传递信息,进行图灵完备的运算.合约依靠被称作以太坊虚拟机(EVM) 字节代码(以太坊 ...

  8. 部署NEP-5智能合约 (第1部分)

    2019独角兽企业重金招聘Python工程师标准>>> 如果您还没搭建私有网络,请参考之前的文章先行搭建. Photo by Markus Spiske on Unsplash 开始 ...

  9. EOS区块链技术开发(二)智能合约

    强烈建议直接去看EOSIO官网上的教程:https://developers.eos.io/ 不要看我的??博文了,以下博文就是我当时随便写写的,现在也没有什么时间详细修改.如果有问题可以提问. 注: ...

最新文章

  1. 【阿里聚安全·安全周刊】Intel芯片级安全漏洞事件|macOS存在漏洞
  2. SQL server 实例教程
  3. html左右飘窗高度不一致,飘窗的最佳尺寸,你可知道?不懂的留着吧!
  4. android自动化优化工具,Auto Optimizer手机性能自动优化App
  5. 开发商微信选房后不退认筹金_网曝!青岛恒大文化旅游城1400余名购房者欲退认筹金,开发商表示.........
  6. 找不到aspnet用户权限的解决方法
  7. Java判断是否为素数
  8. 软件工程入门基本知识
  9. office2016 office2019 office2013 ----一键安装:
  10. Python编程之读取Excel csv格式文件内容
  11. Alluxio代码结构
  12. [《命如草贱》偶感小记]2013-2-17
  13. 关于JS的一些面试题
  14. CentOS 7 磁盘挂载教程
  15. OpenGL 立方体平行斜投影的绘制
  16. 《算法》(第四版)------------图
  17. 208核、6TB内存,阿里云发布全球最强云服务器:挑战摩尔定律极限
  18. Parsing error: missing-semicolon-after-character-reference.
  19. 理财成长之路 | 记账
  20. java十六进制转十进制_java十六进制转十进制

热门文章

  1. 【论文分享】不平衡流量分类方法 DeepFE:ResNet+SE+non-local:Let Imbalance Have Nowhere to Hide
  2. 计算机网络——哈工大
  3. 设立股份有限公司的条件及要求
  4. Controlling Access to the Kubernetes API
  5. 在win10下本地代码推送到GitHub
  6. Windows 10升级如何立即进行,不用等微软分批推送?
  7. python快速打引号_Python:如何向长lis添加单引号
  8. vs2010+cef3的90.6.0版实现把cef3嵌入到mfc窗口
  9. 16进制与10进制转换(Qt)
  10. 拯救湖心的风筝(一道趣味平面几何问题)