合约创建基础

new 关键字创建合约

在 Solidity 中,new关键字是创建合约实例的最基本方式,它就像是一个 “魔法钥匙”,能够在以太坊区块链上生成一个全新的合约实例。使用new关键字创建合约的过程非常直观,就像我们在其他编程语言中创建对象一样。下面通过一个简单的示例来展示如何使用new关键字创建合约:

pragma solidity ^0.8.0;
// 定义一个简单的合约
contract SimpleContract {uint256 value;// 构造函数,用于初始化valueconstructor(uint256 _value) {value = _value;}// 函数,用于获取value的值function getValue() public view returns (uint256) {return value;}
}contract ContractCreator {function createSimpleContract() public returns (SimpleContract) {// 使用new关键字创建SimpleContract合约实例SimpleContract newContract = new SimpleContract(10); return newContract;}
}

在上述代码中,首先定义了一个名为SimpleContract的合约,它包含一个状态变量value和一个构造函数,构造函数用于初始化value的值。然后定义了一个ContractCreator合约,在ContractCreator合约中,createSimpleContract函数使用new关键字创建了一个SimpleContract合约的实例,并传入初始值10 。最后返回新创建的合约实例。

当我们在以太坊区块链上部署ContractCreator合约,并调用createSimpleContract函数时,就会在区块链上创建一个新的SimpleContract合约实例,并且这个实例的value值会被初始化为10 。通过这种方式,我们可以动态地在区块链上创建和部署新的合约,为 Web3 应用的开发提供了极大的灵活性。

构造函数的作用

构造函数是合约中的一个特殊函数,它在合约创建时被自动调用,就像是合约的 “初始化向导”,负责为合约的状态变量设置初始值,以及执行其他必要的初始化操作。构造函数的重要性不言而喻,它确保了合约在创建后处于一个正确的初始状态,为后续的功能实现奠定了基础。

构造函数的名称与合约名称相同(在 Solidity 0.4.22 及之后版本,也可以使用constructor关键字定义构造函数 ),并且在合约的生命周期中只执行一次。例如,在前面的SimpleContract合约中,构造函数的定义如下:

constructor(uint256 _value) {value = _value;
}

这个构造函数接受一个uint256类型的参数_value,并将其赋值给状态变量value 。当使用new关键字创建SimpleContract合约实例时,构造函数会被自动调用,传入的参数10会被用来初始化value ,使得新创建的合约实例中的value值为10 。

构造函数还可以执行更复杂的初始化逻辑,比如设置合约的所有者、初始化多个状态变量、调用其他合约的初始化函数等。例如,下面的合约中,构造函数不仅初始化了状态变量,还设置了合约的所有者:

pragma solidity ^0.8.0;contract OwnedContract {address owner;uint256 initialValue;constructor(uint256 _initialValue) {owner = msg.sender;initialValue = _initialValue;}function getOwner() public view returns (address) {return owner;}function getInitialValue() public view returns (uint256) {return initialValue;}
}

在这个合约中,构造函数将msg.sender(即合约的部署者)赋值给owner变量,同时将传入的参数_initialValue赋值给initialValue变量。这样,在合约创建后,就可以通过getOwner和getInitialValue函数获取合约的所有者和初始值。

多种创建方式深度剖析

工厂合约模式

代码示例:下面通过一个具体的代码示例来深入理解工厂合约模式:

pragma solidity ^0.8.0;// 定义一个简单的Token合约
contract Token {address public owner;uint256 public totalSupply;constructor(uint256 _initialSupply) {owner = msg.sender;totalSupply = _initialSupply;}function transfer(address to, uint256 amount) public {require(msg.sender == owner, "Only owner can transfer");totalSupply -= amount;// 这里可以添加实际的转账逻辑,例如更新余额等}
}// 定义Token工厂合约
contract TokenFactory {// 用于存储创建的Token合约地址Token[] public createdTokens; function createToken(uint256 initialSupply) public returns (Token) {// 使用new关键字创建Token合约实例Token newToken = new Token(initialSupply); // 将新创建的Token合约地址添加到数组中createdTokens.push(newToken); return newToken;}function getCreatedTokensCount() public view returns (uint256) {return createdTokens.length;}
}

在上述代码中,首先定义了一个Token合约,它包含了owner和totalSupply两个状态变量,以及constructor和transfer两个函数。constructor函数用于初始化合约的所有者和总供应量,transfer函数用于实现代币的转账功能(这里仅为示例,实际转账逻辑可根据需求完善)。

接着定义了TokenFactory工厂合约,它包含一个createdTokens数组,用于存储所有创建的Token合约地址。createToken函数是工厂合约的核心,它接受一个initialSupply参数,用于指定新创建的Token合约的初始供应量。在函数内部,使用new关键字创建一个新的Token合约实例,并将其添加到createdTokens数组中,最后返回新创建的合约实例。getCreatedTokensCount函数用于获取已经创建的Token合约数量。

当我们部署TokenFactory合约后,可以通过调用createToken函数来创建多个Token合约实例,每个实例都有自己独立的状态和功能,并且可以通过TokenFactory合约对这些实例进行统一管理。

通过库(Library)创建

  1. 库的特性与创建原理:在 Solidity 中,库是一种特殊的合约类型,它主要用于提供无状态的功能。与普通合约不同,库不能存储状态变量,也没有自己的存储空间,这使得库具有更高的可复用性和效率。库的代码在编译时会被嵌入到使用它的合约中,就像在其他编程语言中使用静态函数库一样,从而避免了额外的合约调用开销。

尽管库主要用于提供无状态的功能,但仍然可以在库中创建和部署合约。这是因为库可以访问外部合约的代码和功能,通过使用new关键字,库可以像普通合约一样创建其他合约的实例。例如,假设我们有一个用于创建简单计数器合约的库:

pragma solidity ^0.8.0;// 定义计数器合约
contract Counter {uint256 public count;constructor() {count = 0;}function increment() public {count++;}
}// 定义用于创建Counter合约的库
library CounterDeployer {function deployCounter() external returns (Counter) {return new Counter();}
}

在上述代码中,首先定义了一个Counter合约,它包含一个count状态变量和constructor、increment两个函数。constructor函数用于初始化count为 0,increment函数用于将count加 1。

然后定义了CounterDeployer库,它包含一个deployCounter函数,该函数使用new关键字创建一个新的Counter合约实例,并返回这个实例。通过这种方式,其他合约可以使用CounterDeployer库来创建Counter合约,而无需重复编写创建合约的代码。

  1. 应用场景与案例:在实际开发中,库创建合约的应用场景非常广泛。例如,在开发去中心化金融(DeFi)应用时,可能需要创建大量的借贷合约、流动性池合约等。通过使用库来创建这些合约,可以将创建合约的逻辑封装在库中,提高代码的复用性和可维护性。

以一个简单的借贷应用为例,假设有一个LoanContract合约用于管理借贷业务,我们可以创建一个库来负责创建LoanContract合约实例:

pragma solidity ^0.8.0;// 定义借贷合约
contract LoanContract {address public lender;address public borrower;uint256 public loanAmount;constructor(address _lender, address _borrower, uint256 _loanAmount) {lender = _lender;borrower = _borrower;loanAmount = _loanAmount;}// 其他借贷相关的函数,如还款、计息等
}// 定义用于创建LoanContract合约的库
library LoanDeployer {function deployLoanContract(address _lender, address _borrower, uint256 _loanAmount) external returns (LoanContract) {return new LoanContract(_lender, _borrower, _loanAmount);}
}

在这个例子中,LoanDeployer库的deployLoanContract函数可以根据传入的参数创建新的LoanContract合约实例。其他合约可以通过调用这个函数来快速创建借贷合约,而无需关心合约创建的具体细节。这种方式使得代码结构更加清晰,也方便了后续对借贷合约创建逻辑的修改和扩展。

代理(Proxy)合约创建

  1. 代理模式介绍:代理合约创建是一种在区块链开发中非常重要的模式,它允许合约逻辑的升级而不改变合约地址。在传统的智能合约开发中,一旦合约部署到区块链上,其字节码就无法修改,如果需要对合约进行升级,就必须部署一个新的合约,这会带来一系列问题,如合约地址变更、用户需要重新关联新地址等。代理模式的出现解决了这些问题,它通过引入一个代理合约和一个或多个实现合约,实现了合约逻辑的动态更新。

代理合约就像是一个中间层,它主要负责存储状态变量,并将所有的函数调用转发给实现合约。当外部对代理合约进行调用时,代理合约会根据预先设定的逻辑,将调用委托给相应的实现合约进行处理。实现合约则包含了具体的业务逻辑和功能代码。当需要升级合约逻辑时,只需部署一个新的实现合约,并将代理合约的指向更新为新的实现合约地址,就可以实现合约的无缝升级,而不会影响用户对合约的使用。

这种模式的原理基于 Solidity 中的delegatecall函数调用方式。delegatecall是一种特殊的函数调用,它允许合约调用另一个合约的代码,但使用的是调用者的上下文(包括存储、地址、余额等)。通过delegatecall,代理合约可以借用实现合约的功能,同时保持自己的状态数据不变,从而实现了合约逻辑的升级和状态的持续性。下面通过一个具体的代码示例来展示代理合约的创建和工作原理:

pragma solidity ^0.8.0;// 定义实现合约
contract CounterImplementation {uint256 public count;constructor() {count = 0;}function increment() public {count++;}
}// 定义代理合约
contract CounterProxy {address public implementation;constructor(address _implementation) {implementation = _implementation;}fallback() external payable {address _impl = implementation;assembly {let ptr := mload(0x40)calldatacopy(ptr, 0, calldatasize())let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)let size := returndatasize()returndatacopy(ptr, 0, size)switch result case 0 { revert(ptr, size) } default { return(ptr, size) }}}
}

在上述代码中,首先定义了CounterImplementation实现合约,它包含一个count状态变量和constructor、increment两个函数。constructor函数用于初始化count为 0,increment函数用于将count加 1。

接着定义了CounterProxy代理合约,它包含一个implementation状态变量,用于存储实现合约的地址。constructor函数用于初始化implementation为传入的实现合约地址。fallback函数是代理合约的关键,当代理合约接收到无法识别的函数调用时,会自动进入fallback函数。在fallback函数中,通过内联汇编代码实现了delegatecall操作,将调用委托给implementation指向的实现合约。具体步骤如下:

  • 首先获取内存指针ptr,用于存储调用数据。

  • 使用calldatacopy将调用数据从调用栈复制到内存中。

  • 使用delegatecall调用实现合约的函数,将调用数据传递给实现合约,并返回执行结果。

  • 根据delegatecall的返回结果,处理返回数据或进行错误处理。如果delegatecall执行成功(result不为 0),则将返回数据复制回调用栈并返回;如果执行失败(result为 0),则进行回滚操作。

通过这种方式,代理合约可以将外部的调用转发给实现合约,实现合约逻辑的执行,同时保持代理合约自身的状态不变。当需要升级合约逻辑时,只需部署一个新的CounterImplementation实现合约,并将CounterProxy代理合约的implementation变量更新为新的实现合约地址,就可以实现合约的升级,而不会影响用户对合约的使用。

Assembly 创建

  1. 底层原理:使用内联汇编(Assembly)创建合约是一种深入底层的操作,它允许开发者直接与以太坊虚拟机(EVM)进行交互,实现更加精细的控制和优化。在 Solidity 中,虽然大多数开发者使用高级语言特性进行合约开发,但在某些特定场景下,内联汇编可以提供更高的性能和更多的灵活性。

内联汇编创建合约的底层原理基于 EVM 的操作码。EVM 是以太坊的核心执行环境,它通过一系列的操作码来执行合约代码。在创建合约时,主要使用create操作码。create操作码用于在区块链上创建一个新的合约,并返回新合约的地址。其操作需要三个参数:要发送的以太数量(以 wei 为单位)、指向合约创建字节码(Creation ByteCode)的内存指针以及合约创建字节码的长度。

在 Solidity 中使用内联汇编创建合约时,需要注意以下几点:

  • 内存管理:内联汇编直接操作 EVM 的内存,开发者需要手动管理内存的分配和释放。例如,在获取合约创建字节码的内存指针时,需要考虑动态数组和字符串的长度信息存储位置。在 Solidity 中,动态数组和字符串的前 32 字节用于存储长度信息,因此实际的合约创建字节码数据从第 33 字节开始。在汇编中,可以通过add(_creationCode, 0x20)来跳过这 32 字节,获取实际的字节码数据。

  • 字节码长度获取:可以使用mload(_creationCode)来获取合约创建字节码的长度。这里的_creationCode是指向合约创建字节码的内存指针,mload操作码用于从内存中加载数据。

以下是一个使用内联汇编创建合约的简单示例:

pragma solidity ^0.8.0;contract AssemblyDeployer {function deploy(bytes memory _code) public returns (address addr) {assembly {addr := create(0, add(_code, 0x20), mload(_code))}}
}

在上述代码中,AssemblyDeployer合约包含一个deploy函数,该函数接受一个bytes类型的参数_code,表示合约的创建字节码。在函数内部,通过内联汇编代码使用create操作码创建新的合约。create操作码的第一个参数为 0,表示不发送以太币;第二个参数add(_code, 0x20)用于获取实际的合约创建字节码内存指针;第三个参数mload(_code)用于获取合约创建字节码的长度。最后,将创建的合约地址赋值给addr并返回。

  1. 使用场景与注意事项:内联汇编创建合约适用于一些对性能要求极高,或者需要实现特殊底层功能的场景。例如,在开发一些对 gas 消耗非常敏感的合约时,通过内联汇编可以优化代码,减少不必要的开销,从而降低 gas 消耗。此外,当需要直接访问 EVM 的某些底层功能,而这些功能在 Solidity 高级语言中没有直接接口时,内联汇编也可以派上用场。

然而,使用内联汇编创建合约也存在一定的风险和挑战,需要开发者特别注意:

  • 安全性风险:内联汇编绕过了 Solidity 的许多安全检查机制,这使得代码更容易引入错误和漏洞。例如,在手动管理内存时,如果出现内存越界、悬空指针等问题,可能会导致合约的安全性受到威胁。因此,开发者需要对 EVM 的工作原理有深入的理解,并且在编写内联汇编代码时格外小心,确保代码的正确性和安全性。

  • 代码可读性和可维护性:内联汇编代码通常比 Solidity 高级语言代码更难阅读和理解。由于其语法和操作与底层的 EVM 紧密相关,对于不熟悉 EVM 的开发者来说,理解和维护内联汇编代码可能会非常困难。因此,在使用内联汇编时,应尽量添加详细的注释,以提高代码的可读性和可维护性。同时,除非必要,应尽量避免在大型项目中大量使用内联汇编,以免增加项目的维护成本。

create2 创建

  1. create2是以太坊在君士坦丁堡硬分叉中引入的一个新操作码,它为合约创建带来了一种全新的方式,具有一些独特的优势,其中最显著的就是能够提前确定合约地址。

在传统的合约创建方式中,使用CREATE操作码(在 Solidity 中对应new关键字)创建的合约地址是根据交易发起者(sender)的地址以及交易序号(nonce)来计算确定的。具体计算方式是将 sender 和 nonce 进行 RLP 编码,然后用 Keccak-256 进行哈希计算(伪码表示为keccak256(rlp([sender, nonce])))。由于交易序号nonce会随着每次交易或合约创建而递增,因此在创建合约之前,无法准确预知合约的最终地址。

而create2操作码则改变了这一情况。create2主要是根据创建合约的初始化代码(init_code)及盐(salt)来生成合约地址。其计算方式的伪码表示为keccak256(0xff + sender + salt + keccak256(init_code))。这里的0xff是一个常数,用于避免和CREATE操作码冲突;sender是合约创建者的地址;salt是一个任意的 256 位值,由开发者自由选择;init_code通常就是合约编译生成的字节码。通过这种方式,只要初始化代码和盐值确定,无论在何时何地创建合约,其地址都是固定可预测的。

这种能够提前确定合约地址的特性在许多应用场景中都非常有用。例如,在一些需要预先规划合约地址的项目中,如去中心化交易所(DEX)创建交易对合约时,使用create2可以提前计算出交易对合约的地址,并将其包含在事先发布的交易或文档中,方便后续的交互和操作。此外,在一些涉及状态通道、侧链等复杂的区块链架构中,提前确定合约地址也有助于提高系统的稳定性和可预测性,用create2创建合约:

// (一)模拟去中心化交易所创建币对合约
//以一个简化的去中心化交易所(DEX)为例,我们来展示如何使用工厂合约创建币对合约。在去中心化交易所中,不同的加密货币对需要对应的交易合约来管理交易逻辑和流动性。//首先,定义一个`TokenPair`合约,用于管理单个币对的交易:
pragma solidity ^0.8.0;contract TokenPair {address public tokenA;address public tokenB;constructor(address _tokenA, address _tokenB) {tokenA = _tokenA;tokenB = _tokenB;}// 模拟交易函数,实际应用中需要更复杂的逻辑function trade(uint256 amountA, uint256 amountB) public {// 这里可以添加交易逻辑,如检查余额、更新流动性等}
}

在上述TokenPair合约中,包含了两个状态变量tokenA和tokenB,分别表示币对中的两种代币地址。构造函数接受两个代币地址作为参数,并将它们赋值给对应的状态变量。trade函数用于模拟币对之间的交易,在实际应用中,这个函数需要包含更复杂的逻辑,如检查用户的余额、更新流动性池、处理交易手续费等。

接着,定义一个TokenPairFactory工厂合约,用于创建TokenPair合约实例:

pragma solidity ^0.8.0;
contract TokenPairFactory {// 用于存储创建的TokenPair合约地址mapping(address => mapping(address => address)) public tokenPairs; function createTokenPair(address tokenA, address tokenB) public returns (address) {require(tokenA != tokenB, "Tokens cannot be the same");require(tokenPairs[tokenA][tokenB] == address(0), "Pair already exists");TokenPair newPair = new TokenPair(tokenA, tokenB);tokenPairs[tokenA][tokenB] = address(newPair);tokenPairs[tokenB][tokenA] = address(newPair);return address(newPair);}
}

在TokenPairFactory工厂合约中,使用了一个二维映射tokenPairs来存储创建的TokenPair合约地址。createTokenPair函数是工厂合约的核心函数,它接受两个代币地址tokenA和tokenB作为参数。在函数内部,首先进行一些条件检查,确保传入的两个代币地址不同,并且当前币对的合约尚未创建。然后使用new关键字创建一个新的TokenPair合约实例,并将其地址存储到tokenPairs映射中,最后返回新创建的合约地址。

通过这种方式,当有新的币对需要在去中心化交易所中进行交易时,只需要调用TokenPairFactory的createTokenPair函数,就可以快速创建对应的TokenPair合约实例,实现了币对合约创建的自动化和规范化,提高了去中心化交易所的可扩展性和灵活性。


以知名的去中心化交易所 Uniswap V2 为例,Uniswap V2 是以太坊上最具代表性的去中心化交易所之一,其创新的自动做市商(AMM)模式和高效的合约设计,为众多 Web3 项目提供了借鉴。

Uniswap V2 主要包含三个核心合约:UniswapV2Factory(工厂合约)、UniswapV2Pair(币对合约)和UniswapV2Router(路由合约) ,其中与合约创建密切相关的是UniswapV2Factory和UniswapV2Pair。

UniswapV2Factory合约负责创建和管理UniswapV2Pair币对合约。它的核心功能是通过createPair函数来创建新的币对合约实例:

function createPair(address tokenA, address tokenB) external returns (address pair) {require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');(address token0, address token1) = tokenA < tokenB? (tokenA, tokenB) : (tokenB, tokenA);require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficientbytes memory bytecode = type(UniswapV2Pair).creationCode;bytes32 salt = keccak256(abi.encodePacked(token0, token1));assembly {pair := create2(0, add(bytecode, 32), mload(bytecode), salt)}IUniswapV2Pair(pair).initialize(token0, token1);getPair[token0][token1] = pair;getPair[token1][token0] = pair; // populate mapping in the reverse directionallPairs.push(pair);emit PairCreated(token0, token1, pair, allPairs.length);
}

在上述代码中,createPair函数首先对传入的两个代币地址tokenA和tokenB进行检查,确保它们不相同且不为零地址,同时检查当前币对合约是否已经存在。然后,获取UniswapV2Pair合约的创建字节码bytecode,并根据两个代币地址生成一个盐值salt 。接下来,使用create2操作码创建新的UniswapV2Pair合约实例,create2操作码可以根据字节码和盐值确定性地生成合约地址,这在前面介绍create2时已详细说明。创建合约后,调用合约的initialize函数进行初始化,将两个代币地址设置到合约中。最后,将新创建的合约地址存储到getPair映射中,并添加到allPairs数组中,同时触发PairCreated事件,通知外部应用新的币对合约已创建。

UniswapV2Pair合约则负责管理单个币对的流动性和交易逻辑。它包含了流动性的添加和移除、代币兑换等功能:

contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {address public factory;address public token0;address public token1;// 其他状态变量和函数定义...function initialize(address _token0, address _token1) external {require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient checktoken0 = _token0;token1 = _token1;// 其他初始化逻辑...}// 流动性添加函数function mint(address to) external returns (uint256 liquidity) {// 流动性添加逻辑...}// 代币兑换函数function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external {// 兑换逻辑...}
}

在UniswapV2Pair合约中,initialize函数在合约创建后被调用,用于初始化合约的状态变量,包括设置工厂合约地址factory、两个代币地址token0和token1 。mint函数用于添加流动性,用户可以将两种代币存入合约,合约会根据存入的数量计算并发放流动性代币。swap函数则实现了代币之间的兑换功能,根据用户传入的兑换数量和目标地址,在合约内部进行代币的交换操作。

通过对 Uniswap V2 合约创建逻辑的分析,可以看到其设计的精妙之处。UniswapV2Factory工厂合约通过create2操作码确保了币对合约地址的可预测性和唯一性,同时方便了合约的管理和查询。UniswapV2Pair合约则专注于单个币对的流动性管理和交易处理,使得整个去中心化交易所的架构清晰、功能明确,为用户提供了高效、可靠的交易服务。这种设计思路在许多其他 Web3 项目中也得到了广泛应用和借鉴,成为了 Web3 开发中的经典范例。

注意事项

重入攻击

在合约创建过程中,安全是至关重要的。以重入攻击为例,这是一种常见且危险的攻击方式,它利用了合约在处理外部调用时的漏洞,使得攻击者能够多次进入合约的关键函数,从而实现非法操作,如多次提取资金等。在创建合约时,为了避免重入攻击,可采用 “检查 - 效果 - 交互(CEI)” 模式,即先进行条件检查,再执行状态修改等效果操作,最后进行外部交互。例如,在一个简单的取款合约中:

contract SafeWithdraw {mapping(address => uint) public balances;function withdraw(uint amount) public {require(balances[msg.sender] >= amount, "Insufficient balance"); // 检查balances[msg.sender] -= amount; // 效果(bool success, ) = msg.sender.call{value: amount}(""); // 交互require(success, "Failed to send Ether");}
}

在上述代码中,首先检查用户的余额是否足够,然后更新用户的余额,最后进行转账操作,这样就避免了在转账过程中被重入攻击导致余额错误减少的问题。

另外,还可以使用互斥锁来防止重入攻击。通过定义一个状态变量来表示合约是否正在执行关键操作,在进入关键函数时检查该变量,若合约正在执行操作则阻止再次进入,操作完成后再释放锁。例如:

contract ReentrancyGuard {bool internal locked;modifier noReentrant() {require(!locked, "No re-entrancy");locked = true;_;locked = false;}mapping(address => uint) public balances;function withdraw(uint amount) public noReentrant {require(balances[msg.sender] >= amount, "Insufficient balance");balances[msg.sender] -= amount;(bool success, ) = msg.sender.call{value: amount}("");require(success, "Failed to send Ether");}
}

在这个合约中,noReentrant修饰符起到了互斥锁的作用,确保在withdraw函数执行期间不会被重入调用。

其他问题

  • Gas 不足
    当部署合约或调用创建合约的函数时,如果消耗的 Gas 超过了设置的 Gas 上限,就会导致交易失败。例如,在部署一个复杂的合约时,由于合约代码量大、逻辑复杂,可能会消耗较多的 Gas。解决方法是在部署或调用函数时,适当增加 Gas 的设置。在使用 Web3.js 进行合约部署时,可以通过设置gas参数来调整 Gas 的用量:
const MyContract = new web3.eth.Contract(abi, bytecode);
MyContract.deploy({ data: bytecode, arguments: [arg1, arg2] }).send({ from: account.address, gas: 5000000 }) // 增加gas值.on('error', function(error) {console.error(error);}).on('transactionHash', function(transactionHash) {console.log('Transaction Hash:', transactionHash);}).then(function(newContractInstance) {console.log('Contract deployed at:', newContractInstance.options.address);});
  • 类型不匹配
    Solidity 是一种静态类型语言,变量和函数参数都有明确的类型。如果在创建合约时,传递的参数类型与函数定义的类型不匹配,就会导致编译错误。例如,在调用合约的构造函数时,传入的参数类型错误:
contract Example {uint256 value;constructor(uint256 _value) {value = _value;}
}contract Caller {function createExample() public {string memory wrongType = "10"; // 错误的类型,应为uint256Example newExample = new Example(wrongType); // 编译错误}
}

解决方法是确保传递的参数类型与函数定义的类型一致,将上述代码中的wrongType改为正确的uint256类型:

contract Caller {function createExample() public {uint256 correctType = 10;Example newExample = new Example(correctType);}
}
  • 地址为空
    在合约创建过程中,如果涉及到地址相关的操作,如设置合约的所有者地址、调用其他合约的地址等,若使用了空地址(address(0)),可能会导致逻辑错误或安全问题。例如,在一个需要设置所有者地址的合约中:
contract Owned {address owner;constructor(address _owner) {owner = _owner;}function doSomething() public {require(msg.sender == owner, "Only owner can perform this action");// 执行操作}
}contract Creator {function createOwned() public {address emptyAddress = address(0);Owned newOwned = new Owned(emptyAddress); // 错误,使用了空地址}
}

解决方法是在使用地址时,确保地址不为空。在上述代码中,应传入一个有效的地址:

contract Creator {function createOwned() public {address validAddress = msg.sender;Owned newOwned = new Owned(validAddress);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/910232.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/910232.shtml
英文地址,请注明出处:http://en.pswp.cn/news/910232.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OCR大模型,破解金融文档处理困境,从文字识别到文字理解

金融机构在日常运营中处理海量文档。这些文档类型多样&#xff0c;格式复杂&#xff0c;是业务运营的基础。如何高效、准确地处理这些文档&#xff0c;直接影响机构的运营效率与风险控制水平。新一代的OCR大模型技术为此提供了有效的解决方案。它提升了文档处理的自动化程度与数…

2025.6.21笔记(2)

1.编写一个程序&#xff0c;输入一个整数&#xff0c;判断它是奇数还是偶数 解题思路&#xff1a; 1.因为要判断输入的数是奇数还是偶数&#xff0c;所以要用到if判断 2.判读奇偶数&#xff1a;如果这个数%20&#xff0c;则它为偶数&#xff0c;如果这个数%2!0&#xff0c;则…

【Ambari3.0.0 部署】Step7—Mariadb初始化-适用于el8

如果有其他系统部署需求可以参考原文 https://doc.janettr.com/install/manual/ MariaDB 10 是 Ambari 及大数据平台的常见数据库方案。本文适配 Rocky Linux 8.10&#xff0c;涵盖 MariaDB 10.11 推荐安装、YUM 源配置、参数优化、初始化和安全设置&#xff0c;帮助你一步到位…

SpringBoot电脑商城项目--删除收获地址+热销排行

删除收获地址 1 删除收获地址-持久层 1.1 规划sql语句 在删除操作之前判断该数据是否存在&#xff0c;判断该条地址的归属是否是当前的用户执行删除收货地址的操作 delete from t_address where aid? 如果用户删除的时默认地址&#xff0c;将剩下地址的某一条作为默认收货地…

MIMIC-III 数据集文件简介

文件简介&#xff1a; 共26个文件 admissions.csv 患者入院信息&#xff08;入院时间、出院时间、入院类型、科室等&#xff09;。 callout.csv ICU 外科室请求 ICU 会诊的呼叫记录。 caregivers.csv 护理患者的医护人员信息&#xff08;身份、角色等&#xff09;。…

UL/CE双认证!光宝MOC3052-A双向可控硅输出光耦 智能家居/工业控制必备!

光宝MOC3052-A双向可控硅输出光耦详解 1. 产品定位 MOC3052-A 是光宝科技&#xff08;Lite-On&#xff09;推出的 双向可控硅驱动光耦&#xff0c;属于光电隔离型半导体器件&#xff0c;主要用于交流负载的隔离控制&#xff0c;实现低压控制电路&#xff08;如MCU&#xff09;…

让没有小窗播放的视频网站的视频小窗播放

让没有小窗播放的视频网站的视频小窗播放 // 视频小窗播放控制台脚本 // 将此代码复制到浏览器控制台运行 // 运行后&#xff0c;页面中的视频将添加小窗播放功能(function() {// 获取页面中的所有video元素const videos document.querySelectorAll(video);if (videos.length…

Linux内核在启动过程中挂载根文件系统rootfs的过程

一、挂载根文件系统rootfs的过程&#xff1a; 1. ‌初始虚拟根文件系统的挂载‌ 内核启动时首先会创建并挂载一个‌临时虚拟根文件系统&#xff08;如initramfs或rootfs&#xff09;‌‌15。该阶段主要作用&#xff1a; 提供基础的设备节点和目录结构&#xff0c;确保内核能访…

【LeetCode】力扣题——轮转数组、消失的数字、数组串联

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训 &#x1f349;学习方向&#xff1a;C/C方向 ⭐️人生格言&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;…

Java Stream详解

Java Stream详解 Stream 是 Java 8 引入的流式数据处理工具&#xff0c;可以像流水线一样对集合数据进行高效操作&#xff08;过滤、转换、统计等&#xff09;。核心特点&#xff1a; 链式操作&#xff1a;支持多个操作串联不修改原始数据&#xff1a;生成新结果支持并行处理…

Java回归循环理解

一、Java循环的四种 1. 传统for循环 - 精确控制的首选 // 遍历数组 int[] numbers {1, 2, 3, 4, 5}; for (int i 0; i < numbers.length; i) {System.out.println(numbers[i]); }// 嵌套示例&#xff1a;矩阵遍历 int[][] matrix {{1, 2}, {3, 4}}; for (int row 0; r…

飞腾D2000金融工控主板,点亮经济高质量发展

近年来&#xff0c;国家不断推出金融行业的政策和法规&#xff0c;推动金融业高质量发展。在国家大力推进金融行业改革和创新的大环境下&#xff0c;金融工控主板市场也迎来了新的发展机遇。随着国产CPU技术的不断突破&#xff0c;以及我国对金融安全重视程度的提高&#xff0c…

SimpleITK——创建nrrd体素模型

在介绍如何生成nrrd前&#xff0c;了解一下为什么医学影像上一般使用nrrd的体素模型&#xff1f; 为什么医学影像上一般使用nrrd的体素模型&#xff1f; 在医学影像领域&#xff0c;‌NRRD&#xff08;Nearly Raw Raster Data&#xff09;格式‌被广泛用于存储体素模型&#x…

Docker容器部署KES

一、安装部署 1&#xff0c;导入镜像 #导入镜像&#xff08;root用户&#xff09; [rootnode docker ]# mv kdb_x86_64_V008R006C009B0014.tar kingbase.tar [rootnode docker]# docker load -i kingbase.tar#查看镜像&#xff08;root用户&#xff09; [rootnode docker]# d…

C++基础练习 sort函数,用于排序函数

题目&#xff1a; https://acm.hdu.edu.cn/showproblem.php?pid2039 解答&#xff1a; #include <iostream> #include <cmath> #include <algorithm> using namespace std;double a[3]; int main(){int n;cin>>n;while(n--){cin>>a[0]>>…

棱镜观察|EMB“重构”卡钳,车企降本压力与Brembo困局

传统制动卡钳市场&#xff0c;正在迎来变革时刻。 一直以来&#xff0c;采埃孚、大陆集团、日立安斯泰莫等外资供应商占据中国乘用车卡钳前装市场&#xff08;包括前制动卡钳和后集成EPB卡钳&#xff09;的半壁江山。同时&#xff0c;伯特利、亚太股份、万向、弗迪等中国供应商…

《颠覆传统:CSS遮罩的图像创意设计指南》

想象有一块神奇的模板&#xff0c;上面有各种形状的镂空区域&#xff0c;当我们将这块模板覆盖在图像上时&#xff0c;只有透过镂空区域才能看到图像的部分&#xff0c;而模板遮挡的地方则被隐藏起来&#xff0c;这便是CSS遮罩的核心概念。遮罩&#xff0c;简单来说&#xff0c…

5.基于神经网络的时间序列预测

近年来&#xff0c;已经开发了一些深度学习方法并将其应用于单变量时间预测场景&#xff0c;其中时间序列由在等时间增量上按顺序记录的单个观测数据组成。 5.1 将深度学习用于时间序列预测的原因 机器学习的目标是提取特征来训练模型。模型将输入数据&#xff08;例如图片&am…

【软考高级系统架构论文】论软件设计方法及其应用

论文真题 软件设计 (Software Design,SD) 根据软件需求规格说明书设计软件系统的整体结构、划分功能模块、确定每个模块的实现算法以及程序流程等,形成软件的具体设计方案。软件设计把许多事物和问题按不同的层次和角度进行抽象,将问题或事物进行模块化分解,以便更容易解决…

什么是水平扩展

什么是水平扩展 在现代系统架构设计中&#xff0c;可扩展性&#xff08;Scalability&#xff09;是衡量系统面对业务增长时应对能力的重要指标。而“水平扩展”&#xff08;Horizontal Scaling&#xff09;&#xff0c;又称为“横向扩展”或“扩容节点”&#xff0c;正是应对高…