您正在查看: Ethereum 分类下的文章

Web3.py 安装和使用

安装python3

 sudo apt-get install python3-dev -y

设置python3默认

sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 150
sudo update-alternatives --config python

安装pip3

sudo apt-get install python3-pip

安装web3

pip3 install web3

测试合约代码

import time
from web3 import Web3
from web3.contract import Contract, ContractFunctions
from web3 import Web3

false = False
true = True
CONTRACT_ADDRESS = "0xD8B0c34024deA0B9ABaf187E58Dbb3e81a033367"
CONTRACT_ABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"burn","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_value","type":"uint256"}],"name":"burnFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"},{"name":"tokenName","type":"string"},{"name":"tokenSymbol","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burn","type":"event"}]

w3 = Web3(Web3.HTTPProvider('http://。。。:8545'))
acct = w3.eth.account.privateKeyToAccount("e9bc9ae61053。。。。。69fd59d7986b9f4f49c025cc")

if __name__ == "__main__":
    print("getTransactionCount:", w3.eth.getTransactionCount(acct.address))
    print("gasPrice:", w3.eth.gasPrice)

    contract = w3.eth.contract(address=Web3.toChecksumAddress(CONTRACT_ADDRESS), abi=CONTRACT_ABI)
    TokenConstruct_txn = contract.functions.transfer(Web3.toChecksumAddress("0xc65dD4299C682f335d6e15e2B5774D015E01E479"), w3.toWei(2, 'ether')).buildTransaction({
        'from': acct.address,
        'nonce': w3.eth.getTransactionCount(acct.address),
        'gas': 100000,
        'gasPrice': w3.eth.gasPrice})
    TokenSigned = acct.signTransaction(TokenConstruct_txn)
    print("Pre-calculated transactionHash:", w3.toHex(w3.sha3(TokenSigned.rawTransaction)))
    TokenTx_hash = w3.eth.sendRawTransaction(TokenSigned.rawTransaction)
    print("transactionHash:", TokenTx_hash.hex())
    #TokenTx_receipt = w3.eth.waitForTransactionReceipt(TokenTx_hash)
    #TokenContract_address = TokenTx_receipt['contractAddress']
    #print("Contract Deployed At:", TokenContract_address)

参考

https://web3py.readthedocs.io/en/latest/quickstart.html

常见问题

  1. Command "python setup.py egg_info" failed with error code 1
    pip3 install --upgrade setuptools
    python3 -m pip install --upgrade pip

基于以太坊定制链部署Uniswap

项目定制

由于Uniswap 是为以太坊公链设计的,所以前端里面和合约有一些配置是代码写死的,所以我们需要先修改下

前端修改

前端代码地址:https://github.com/Uniswap/uniswap-interface
前端依赖以下几个npm包

  • uniswap-sdk @uniswap/sdk
  • default-token-list @uniswap/default-token-list

主要修改以下几点

修改uniswap-sdk

代码地址:https://github.com/Uniswap/uniswap-v2-sdk
我们先看下前端依赖的sdk版本

"@uniswap/sdk": "3.0.3",

可以看到项目依赖的是3.0.3,查看代码,并没有相应的tag..,只是个commit,这个仓库git用的真是令人发指。。

由于需要我们自己维护这个库的数据,所以我们直接基于上面的commit fork下。
fork后的测试仓库:https://github.com/cppfuns/uniswap-sdk

修改网络

由于目前Uniswap支持eth主网,以及各个测试网,这并不是我们需要的,我们的定制链只支持主网和测试网。
修改ChainId为所需要的id以及选项

export enum ChainId {
  MAINNET = 65524,
  TESTNET = 65525,
}

修改合约地址

这个等后面部署完合约再来修改,先记下
然后修改下工厂合约的地址和code hash

修改WETH地址

在定制链主网和测试网上分别部署WETH合约,修改对应的合约地址

export const WETH = {
  [ChainId.MAINNET]: new Token(ChainId.MAINNET, '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 18, 'WETH', 'Wrapped Ether'),
  [ChainId.TESTNET]: new Token(ChainId.TESTNET, '0x73DcdfeBe2b1Db8FAe6f0A5AA0f35C3BaDa6811A', 18, 'WETH', 'Wrapped Ether')
}

修改default-token-list

fork: https://github.com/cppfuns/default-token-list.git
根据网络需要修改相应的配置

修改uniswap-interface

修改运行节点环境

由于我们是定制链,没有infura,所以修改为我们自己定制链的网络环境

REACT_APP_CHAIN_ID="链id"
REACT_APP_NETWORK_URL="http://节点地址"

修改网络配置

NETWORK_LABELS默认的主网不需要添加

const NETWORK_LABELS: { [chainId in ChainId]?: string } = {
  [ChainId.TESTNET]: 'testnet'
}

FormaticSupportedChains

type FormaticSupportedChains = Extract<ChainId, ChainId.MAINNET | ChainId.TESTNET>

CHAIN_ID_NETWORK_ARGUMENT

const CHAIN_ID_NETWORK_ARGUMENT: { readonly [chainId in FormaticSupportedChains]: string | undefined } = {
  [ChainId.MAINNET]: undefined,
  [ChainId.TESTNET]: 'testnet'
}

EMPTY_LIST

const EMPTY_LIST: TokenAddressMap = {
  [ChainId.TESTNET]: {},
  [ChainId.MAINNET]: {}
}

ETHERSCAN_PREFIXES

const ETHERSCAN_PREFIXES: { [chainId in ChainId]: string } = {
  65524: '',
  65525: 'testnet.'
}

prefix

const prefix = `https://${ETHERSCAN_PREFIXES[chainId] || ETHERSCAN_PREFIXES[65524]}etherscan.io`
可支持的链id

修改MetaMask可以支持的链id,否则无法连接

export const injected = new InjectedConnector({
  supportedChainIds: [65524, 65525]
})

修改合约地址

下面提到的合约,都可以去https://etherscan.io/address/合约地址#code 去查看,然后编译,部署到自己的定制网络中

修改ROUTER_ADDRESS

ROUTER_ADDRESS

各个主网的代币地址

代币地址

export const DAI = new Token(ChainId.MAINNET, '0x5D0624A7401ecB5075eDFd4855D3b9d8909Da744', 18, 'DAI', 'Dai Stablecoin')
export const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD')
export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound')
export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8, 'WBTC', 'Wrapped BTC')
export const GOVERNANCE_ADDRESS = '0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F'
export const TIMELOCK_ADDRESS = '0x1a9C8182C09F50C8318d769245beA52c32BE35BC'
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
修改对应网络UNI地址

修改代码

export const UNI: { [chainId in ChainId]: Token } = {
  [ChainId.MAINNET]: new Token(ChainId.MAINNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
  [ChainId.TESTNET]: new Token(ChainId.TESTNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap')
}
修改WETH_ONLY地址

WETH_ONLY

const WETH_ONLY: ChainTokenList = {
  [ChainId.MAINNET]: [WETH[ChainId.MAINNET]],
  [ChainId.TESTNET]: [WETH[ChainId.TESTNET]]
}
修改多签合约地址

MULTICALL_NETWORKS

const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
  [ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441',
  [ChainId.TESTNET]: '0x2A16f8A9C88Ddaad5928D85Ce065940b7b25E11c'
}
修改V1_FACTORY_ADDRESSES

V1_FACTORY_ADDRESSES v1版本暂时用不到,修改只是为了网络配置

修改getColorFromToken

getColorFromToken,特殊地址,主要是为了网络配置

修改useENSRegistrarContract

useENSRegistrarContract,特殊地址,主要是为了网络配置

修改代币获取列表

在线获取地址

修改获取地址,由于目前没有在线地址,所以都先暂时删除了

修改未支持的代币列表

列表地址暂时删除

修改依赖包

uniswap-interface

 "@uniswap/sdk": "https://github.com/cppfuns/uniswap-sdk.git",
"@uniswap/default-token-list": "https://github.com/cppfuns/default-token-list.git"

remix 环境配置

安装remixd

npm install remixd -g

将本地合约目录提供给指定remix线上链接

remixd -s 代码目录 --remix-ide 允许链接的remix网站地址

remix网址

国外:http://remix.ethereum.org
国内:http://remix.app.hubwiz.com

然后打开对应的网址本地连接就可以了

Ethereum POS + POA链系统合约测试

文章作用

本片文章主要是为了满足没有接触过Ethereum合约的同学,熟悉当前系统合约,以及了解Ethereum合约测试的交互流程。
基于文中所对应的系统合约,主要是测试阶段会尽量详细以及给出预计的测试结果做对比。
在相关系统合约未开源之前,不建议提前浏览此文章。

POS + POA 系统合约开源地址

待开源

合约组成

Validators 验证者合约

主要数据结构如下

// 验证者数据状态
enum Status {
    Inactivated,                       // 未激活
    Activated,                         // 已激活
    Disabled                           // 已禁用
}

// 验证者描述信息
struct Description {
    string nickname;                    // 昵称
    string location;                    // 位置
    string website;                     // 网址
    string contact;                     // 联系
    string remarks;                     // 备注
}

// 验证者数据结构
struct Validator {
    address payable rewardReceiveAddress;// 奖励接收地址
    address claimRewardAddress;          // 执行领取奖励地址
    Status status;                       // 节点状态
    uint256 totalStakeCoins;             // 总抵押代币数
    uint256 totalPendingStakeCoins;      // 总待合入的抵押代币数
    Description description;             // 节点描述
    uint256 shareRatioBase;              // 分成比例(1/shareRatioBase)
    uint256 bpPendingClaimCoins;         // 当前节点待领取的奖励
    uint256 issueTotalRewardsCoins;      // 支持者应该分的总奖励
    uint256 issueBalanceRewardsCoins;    // 支持者应该分的剩余奖励
    uint256 totalDisabledCoins;          // 总监禁的代币数(监禁的代币会被分发给其余验证者)
    uint256 bpLastClaimRewardTime;       // 最后领取奖励的时间
    uint256 lastEditValidatorInfoTime;   // 最后一次编辑验证者信息
    address[] stakers;                   // 当前验证者所支持的账户地址
}

// 抵押地址信息
struct StakingInfo {
    uint256 pendingStakeCoins;           // 对该节点待合入的抵押
    uint256 stakeBalanceCoins;           // 抵押的余额
    uint256 lastClaimRewardTime;         // 最后领取奖励的时间
}

// 投票者统一数据
struct UserCoreData {
    address payable rewardReceiveAddress;// 奖励接收地址
    address claimRewardAddress;          // 执行领取奖励地址
    uint256 totalUnstakeBalanceCoins;    // 取消抵押的余额
    uint256 lastUnstakeClaimTime;        // 最后取消抵押时的块高度
}

mapping(address => Validator) validatorInfo;                // 验证者信息
mapping(address => mapping(address => StakingInfo)) staked; // 用户对于各个验证者的抵押数据 (staker => validator => info)
mapping(address => UserCoreData) coreStaked;                // 用户抵押核心数据
address[] public workingValidators;                         // 当前正在工作验证者地址
address[] public topValidators;                             // 当前出块验证者地址(epoch 周期内更新)
address[] public activedValidators;                         // 处于激活状态的验证者地址
uint256 public totalStake;                                  // 全网当前总抵押
uint256 public totalPendingStake;                           // 全网当前待合入的总抵押
uint256 public totalDisabledCoins;                          // 全网历史总罚没

合约对外接口

合约初始化
initialize(address[] calldata vals, address proposalAddr, address punishContractAddr, address configContractAddr, uint256 epoch)

参数

  • vals 初始化出块验证者地址
  • proposalAddr 提案合约地址
  • punishContractAddr 惩罚治理合约地址
  • configContractAddr 全局配置合约地址
  • epoch 出块周期
设置用户奖励相关地址
setUserCoreRewardAddress(address payable rewardReceiveAddress, address claimRewardAddress)

参数

  • rewardReceiveAddress 用户投票奖励接收地址
  • claimRewardAddress 用户奖励待领取地址
抵押投票
stake(address validator, uint256 stakeCoins)

参数

  • validator 将要投票的节点
  • stakeCoins 投票代币数
    如果当前抵押总量大于转账金额,则消费已取消抵押待领取的代币数。
    对于抵押成功后,抵押代币量,会进去待投票数据,等待下一领取周期后,才会进入真正的抵押投票数据
取消投票
unstake(address validator, uint256 unstakeCoins)

参数

  • validator 将要赎回的验证者
  • unstakeCoins 取消投票的代币数
    赎回时,优先扣除待投票数据
    赎回成功后,对应的代币数,将进入待领取(实时)
领取赎回
refund()

领取待赎回的代币,时间间隔默认3day,每次执行unstake都会刷新,已最后一次赎回时间为开始计算时间。

领取出块验证奖励
claimBpReward(address validator)

参数

  • validator 对应的验证者地址
    执行操作的账户地址需要为创建节点信息时执行的claimRewardAddress账户地址
    执行期间会根据创建节点时,设置的shareRatioBase分配比例,进行期间的奖励划分,该验证者会直接收到自己应得的奖励,接收奖励的账户地址为创建节点时配置的rewardReceiveAddress奖励接收账户(对于创世验证节点,初始化为该账户地址)。
    同时记录支持该节点的抵押用户部分的奖励,等待用户方主动过来领取。(如果领取周期内未及时领取的用户奖励,将会合到下一周期统一继续分分配)
领取投票奖励
claimReward(address voter, address validator)

参数

  • voter 投票者账户地址
  • validator 验证者账户地址

操作的执行权限地址可以为当前投票账户地址,也可以为setUserCoreRewardAddress设置中的claimRewardAddress代为领取地址,同理奖励接收地址如果未设置,则为当前投票地址。

更新当前工作验证者地址 (链程序调用)
updateWorkingValidators(address[] memory newSet, uint256 epoch)

参数

  • newSet 当前工作验证者地址组,及为链程序执行getTopValidators()获取的头部地址,
  • epoch 出块周期
上缴出块奖励
distributeBlockReward()

验证节点上缴出块奖励,奖励上缴后,根据各正在工作的验证节点所抵押的比例进行分配。

创建出块验证节点信息
function createOrEditValidator(
        address payable rewardReceiveAddress, // 奖励接收地址
        address claimRewardAddress,           // 奖励代为领取地址
        string calldata nickname,             // 昵称
        string calldata location,             // 位置
        string calldata website,              // 网址
        string calldata contact,              // 联系方式
        uint256 shareRatioBase,               // 奖励分配比例(1/N)
        string calldata remarks               // 备注
    )
重新激活节点
tryReactive(address validator)

当节点处于非Active时,置为激活状态

更新头部出块节点
updateTopValidators()

重新计算头部出块节点地址,为下一周期链程序获取做准备

其他数据获取类接口省略

Proposal 提案合约

struct ProposalInfo {
    address proposer;    // 提案发起者
    address dst;         // 提议谁加入
    string details;      // 描述信息
    uint256 createTime;  // 创建时间
    uint16 agree;        // 同意总数
    uint16 reject;       // 拒绝总数
    bool isEnd;          // 是否已结束 
}

struct VoteInfo {
    address voter;        // 投票者
    uint256 voteTime;     // 投票时间
    uint16 votedTimes;    // 已投票次数
    bool isAgree;         // 是否同意
}

合约接口

合约初始化
initialize(address validatorContractAddr, address configContractAddr)

参数

  • validatorContractAddr 验证者合约地址
  • configContractAddr 全局合约配置地址
创建提案
createProposal(address val, string calldata details)

参数

  • val 申请出块的验证者地址
  • details 备注信息
    通过监听event获取创建完的提案id
取消提案
cancelProposal(bytes32 id)

创建者可取消创建的提案

对提案投票
voteProposal(bytes32 id, bool isAgree)

对于提案,只有正在出块的节点才有资格投票,经过 1/2+1通过,通过后会把提案中的地址添加到验证者列表,状态为Active
默认全局配置,同一个提案,有最多三次的投票修改机会

Punish 惩罚治理合约

稍后补充

PublicBase 通用基类合约

稍后补充

Config 全局配置合约

稍后补充

Ownable 合约权限合约

稍后补充

为模拟测试修改合约

为了测试方便,我们直接使用普通账户模拟出块账户进行合约的调用
需要注释掉下PublicBase合约中的两个判断条件

modifier onlyMiner() {
    //require(msg.sender == block.coinbase, "Miner only");
    _;
}

modifier onlyBlockEpoch(uint256 epoch) {
    //require(block.number % epoch == 0, "Block epoch only");
    _;
}

合约部署步骤

先将各个合约编译好,通过 myetherwallet工具将以下合约部署到链上

  • Validators
  • Proposal
  • Punish
  • Config

各个合约部署完后的地址

合约名 地址
Validators 0x113D0CD580edB0a70e8De5F92C15145f83211318
Proposal 0x00C8D6d9736347f66c069C9285a5b99B8ddEFDC7
Punish 0x8049F22032C2F7b7fD0981014953bFe5CE891373
Config 0xB3bB2C011277DB3CcB66250617437C42Df69455B

初始化各个系统合约

此处是为了模拟出块验证者测试,生产中当前初始化过程将在链程序打第一个块时做相应的初始化,各个初始化参数配置在genesis.json

"config": {
....
    "congress": {
      "period": 3,
      "epoch": 200,
      "validatorsContractName": "validators",
      "punishContractName": "punish",
      "proposalContractName": "proposal",
      "configContractName": "config",
      "validatorsContractAddr": "0x113D0CD580edB0a70e8De5F92C15145f83211318",
      "punishContractAddr": "0x8049F22032C2F7b7fD0981014953bFe5CE891373",
      "proposalContractAddr": "0x00C8D6d9736347f66c069C9285a5b99B8ddEFDC7",
      "configContractAddr": "0xB3bB2C011277DB3CcB66250617437C42Df69455B"
    }
....
  }

相应的合约bytecode 也会初始化加到genesis.json中的alloc里的各个对应地址。
此篇文章我们只讲模拟测试,更灵活控制测试的步骤。

初始化Validators

对于后面的合约交互统一使用myetherwallet工具

合约测试步骤

稍后补充

备注

文中所涉及的系统合约暂时并没有开源,等后面计划开源后,再做补充

参考

https://github.com/HuobiGroup/huobi-eco-chain
https://github.com/HuobiGroup/huobi-eco-contracts