From b0409f5c428d4b415b3dbdff11367ebabe9a59b6 Mon Sep 17 00:00:00 2001 From: Peter John Bushnell Date: Fri, 20 Sep 2024 10:04:32 +0100 Subject: [PATCH 1/2] Upgrade DST20 contract to allow 1:1 splits (#3059) * added test to upgrade locked token after full release * added test and fix behaviour in V2 contract. should be moved to V3 * Upgrade contract to allow 1:1 splits --------- Co-authored-by: kuegi <29012906+kuegi@users.noreply.github.com> --- lib/ain-contracts/build.rs | 1 + lib/ain-contracts/dst20_v3/DST20V3.sol | 605 ++++++++++++++++++ .../dst20_v3/IDST20Upgradeable.sol | 11 + lib/ain-contracts/src/lib.rs | 22 + lib/ain-cpp-imports/src/bridge.rs | 1 + lib/ain-cpp-imports/src/lib.rs | 8 + lib/ain-evm/src/contract/dst20.rs | 20 +- lib/ain-evm/src/evm.rs | 39 +- src/ffi/ffiexports.cpp | 4 + src/ffi/ffiexports.h | 1 + test/functional/feature_evm_token_split.py | 60 +- test/functional/feature_restart_interest.py | 1 - test/functional/feature_restartdtokens.py | 51 +- 13 files changed, 798 insertions(+), 26 deletions(-) create mode 100644 lib/ain-contracts/dst20_v3/DST20V3.sol create mode 100644 lib/ain-contracts/dst20_v3/IDST20Upgradeable.sol diff --git a/lib/ain-contracts/build.rs b/lib/ain-contracts/build.rs index 7bfcc42cc98..00c0143393c 100644 --- a/lib/ain-contracts/build.rs +++ b/lib/ain-contracts/build.rs @@ -22,6 +22,7 @@ fn main() -> Result<()> { ("dst20", "DST20"), ("dst20_v1", "DST20V1"), ("dst20_v2", "DST20V2"), + ("dst20_v3", "DST20V3"), ]; for (sol_project_name, contract_name) in contracts { diff --git a/lib/ain-contracts/dst20_v3/DST20V3.sol b/lib/ain-contracts/dst20_v3/DST20V3.sol new file mode 100644 index 00000000000..0dd500ec490 --- /dev/null +++ b/lib/ain-contracts/dst20_v3/DST20V3.sol @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: MIT +// File: @openzeppelin/contracts@4.9.2/utils/Context.sol + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts@4.9.2/token/ERC20/IERC20.sol + +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance( + address owner, + address spender + ) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + +// File: @openzeppelin/contracts@4.9.2/token/ERC20/extensions/IERC20Metadata.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +// File: @openzeppelin/contracts@4.9.2/token/ERC20/ERC20.sol + +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) + +import "./IDST20Upgradeable.sol"; +pragma solidity ^0.8.0; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ + +contract ERC20 is Context, IERC20, IERC20Metadata, IDST20Upgradeable { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf( + address account + ) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer( + address to, + uint256 amount + ) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance( + address owner, + address spender + ) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve( + address spender, + uint256 amount + ) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance( + address spender, + uint256 addedValue + ) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require( + currentAllowance >= subtractedValue, + "ERC20: decreased allowance below zero" + ); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require( + fromBalance >= amount, + "ERC20: transfer amount exceeds balance" + ); + unchecked { + _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + _balances[to] += amount; + } + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + _balances[account] += amount; + } + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + // Overflow not possible: amount <= accountBalance <= totalSupply. + _totalSupply -= amount; + } + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require( + currentAllowance >= amount, + "ERC20: insufficient allowance" + ); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + function upgradeToken( + uint256 amount + ) public virtual override returns (address, uint256) { + address precompileAddress = address(0x0a); + bytes memory inputData = abi.encode(msg.sender, address(this), amount); + bytes memory outputData = new bytes(64); + bool success; + + assembly { + success := call( + gas(), + precompileAddress, + 0, + add(inputData, 32), + mload(inputData), + add(outputData, 32), + mload(outputData) + ) + } + require(success, "Precompile call failed"); + + (address newTokenContractAddress, uint256 newAmount) = abi.decode( + outputData, + (address, uint256) + ); + + emit UpgradeResult(newTokenContractAddress, newAmount); + + // Upgrade available + if (newTokenContractAddress != address(this)) { + _burn(msg.sender, amount); + IERC20(newTokenContractAddress).transfer(msg.sender, newAmount); + } + + return (newTokenContractAddress, newAmount); + } +} + +// File: oz.sol + +pragma solidity ^0.8.0; + +contract DST20V3 is ERC20 { + constructor(string memory name, string memory symbol) ERC20(name, symbol) {} +} diff --git a/lib/ain-contracts/dst20_v3/IDST20Upgradeable.sol b/lib/ain-contracts/dst20_v3/IDST20Upgradeable.sol new file mode 100644 index 00000000000..b5114572edd --- /dev/null +++ b/lib/ain-contracts/dst20_v3/IDST20Upgradeable.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IDST20Upgradeable { + event UpgradeResult( + address indexed newTokenContractAddress, + uint256 newAmount + ); + + function upgradeToken(uint256 amount) external returns (address, uint256); +} diff --git a/lib/ain-contracts/src/lib.rs b/lib/ain-contracts/src/lib.rs index 703a7b11db4..bae351e2585 100644 --- a/lib/ain-contracts/src/lib.rs +++ b/lib/ain-contracts/src/lib.rs @@ -213,6 +213,24 @@ lazy_static::lazy_static! { fixed_address: H160(slice_20b!(INTRINSICS_ADDR_PREFIX_BYTE, 0x5)) } }; + + pub static ref DST20_V3_CONTRACT: FixedContract = { + let bytecode = solc_artifact_bytecode_str!( + "dst20_v3", "deployed_bytecode.json" + ); + let input = solc_artifact_bytecode_str!( + "dst20_v3", "bytecode.json" + ); + + FixedContract { + contract: Contract { + codehash: Blake2Hasher::hash(&bytecode), + runtime_bytecode: bytecode, + init_bytecode: input, + }, + fixed_address: H160(slice_20b!(INTRINSICS_ADDR_PREFIX_BYTE, 0x6)) + } + }; } pub fn get_split_tokens_function() -> ethabi::Function { @@ -370,6 +388,10 @@ pub fn get_dst20_v2_contract() -> FixedContract { DST20_V2_CONTRACT.clone() } +pub fn get_dst20_v3_contract() -> FixedContract { + DST20_V3_CONTRACT.clone() +} + #[cfg(test)] mod test { use super::*; diff --git a/lib/ain-cpp-imports/src/bridge.rs b/lib/ain-cpp-imports/src/bridge.rs index ebb13c400c8..a7f8201def3 100644 --- a/lib/ain-cpp-imports/src/bridge.rs +++ b/lib/ain-cpp-imports/src/bridge.rs @@ -90,6 +90,7 @@ pub mod ffi { fn isEthDebugTraceRPCEnabled() -> bool; fn getEVMSystemTxsFromBlock(block_hash: [u8; 32]) -> Vec; fn getDF23Height() -> u64; + fn getDF24Height() -> u64; fn migrateTokensFromEVM( mnview_ptr: usize, old_amount: TokenAmount, diff --git a/lib/ain-cpp-imports/src/lib.rs b/lib/ain-cpp-imports/src/lib.rs index 9981df329f2..55c8942be83 100644 --- a/lib/ain-cpp-imports/src/lib.rs +++ b/lib/ain-cpp-imports/src/lib.rs @@ -156,6 +156,9 @@ mod ffi { pub fn getDF23Height() -> u64 { unimplemented!("{}", UNIMPL_MSG) } + pub fn getDF24Height() -> u64 { + unimplemented!("{}", UNIMPL_MSG) + } pub fn migrateTokensFromEVM( _mnview_ptr: usize, _old_amount: TokenAmount, @@ -367,6 +370,11 @@ pub fn get_df23_height() -> u64 { ffi::getDF23Height() } +/// Gets the DF23 height +pub fn get_df24_height() -> u64 { + ffi::getDF24Height() +} + /// Send tokens to DVM to split pub fn split_tokens_from_evm( mnview_ptr: usize, diff --git a/lib/ain-evm/src/contract/dst20.rs b/lib/ain-evm/src/contract/dst20.rs index 25720491596..bf9f9d88301 100644 --- a/lib/ain-evm/src/contract/dst20.rs +++ b/lib/ain-evm/src/contract/dst20.rs @@ -1,6 +1,7 @@ use ain_contracts::{ get_dfi_reserved_contract, get_dst20_contract, get_dst20_v1_contract, get_dst20_v2_contract, - get_transfer_domain_contract, Contract, FixedContract, IMPLEMENTATION_SLOT, + get_dst20_v3_contract, get_transfer_domain_contract, Contract, FixedContract, + IMPLEMENTATION_SLOT, }; use anyhow::format_err; use ethereum::{ @@ -86,6 +87,19 @@ pub fn dst20_v2_deploy_info() -> DeployContractInfo { } } +pub fn dst20_v3_deploy_info() -> DeployContractInfo { + let FixedContract { + contract, + fixed_address, + } = get_dst20_v3_contract(); + + DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage: Vec::new(), + } +} + pub fn bridge_dst20_in( backend: &EVMBackend, contract: H160, @@ -245,7 +259,9 @@ pub fn get_dst20_migration_txs(mnview_ptr: usize) -> Result> { } pub fn dst20_name_info(dvm_block: u64, name: &str, symbol: &str) -> Vec<(H256, H256)> { - let contract_address = if dvm_block >= ain_cpp_imports::get_df23_height() { + let contract_address = if dvm_block >= ain_cpp_imports::get_df24_height() { + get_dst20_v3_contract().fixed_address + } else if dvm_block >= ain_cpp_imports::get_df23_height() { get_dst20_v2_contract().fixed_address } else { get_dst20_v1_contract().fixed_address diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 9adf013fd66..263381a3691 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -2,10 +2,10 @@ use std::{path::PathBuf, sync::Arc}; use ain_contracts::{ get_dfi_instrinics_registry_contract, get_dfi_intrinsics_v1_contract, get_dst20_v1_contract, - get_dst20_v2_contract, get_transfer_domain_contract, get_transfer_domain_v1_contract, - IMPLEMENTATION_SLOT, + get_dst20_v2_contract, get_dst20_v3_contract, get_transfer_domain_contract, + get_transfer_domain_v1_contract, IMPLEMENTATION_SLOT, }; -use ain_cpp_imports::{get_df23_height, Attributes}; +use ain_cpp_imports::{get_df23_height, get_df24_height, Attributes}; use anyhow::format_err; use ethereum::{Block, PartialHeader}; use ethereum_types::{Bloom, H160, H256, H64, U256}; @@ -19,8 +19,8 @@ use crate::{ contract::{ deploy_contract_tx, dfi_intrinsics_registry_deploy_info, dfi_intrinsics_v1_deploy_info, dst20::{ - dst20_v1_deploy_info, dst20_v2_deploy_info, get_dst20_migration_txs, - reserve_dst20_namespace, + dst20_v1_deploy_info, dst20_v2_deploy_info, dst20_v3_deploy_info, + get_dst20_migration_txs, reserve_dst20_namespace, }, h160_to_h256, reserve_intrinsics_namespace, transfer_domain_deploy_info, transfer_domain_v1_contract_deploy_info, DeployContractInfo, @@ -331,6 +331,7 @@ impl EVMServices { // reserve DST20 namespace; let is_evm_genesis_block = template.get_block_number() == U256::zero(); let is_df23_fork = template.ctx.dvm_block == get_df23_height(); + let is_df24_fork = template.ctx.dvm_block == get_df24_height(); let mut logs_bloom = template.get_latest_logs_bloom(); let mut executor = AinExecutor::new(&mut template.backend); @@ -504,6 +505,34 @@ impl EVMServices { executor.update_storage(address, storage)?; } + if is_df24_fork { + // Deploy contract with updated upgradeToken function + let DeployContractInfo { + address, + storage, + bytecode, + } = dst20_v3_deploy_info(); + + trace!("deploying {:x?} bytecode {:?}", address, bytecode); + executor.deploy_contract(address, bytecode, storage)?; + + let (tx, receipt) = + deploy_contract_tx(get_dst20_v3_contract().contract.init_bytecode, &base_fee)?; + template.transactions.push(TemplateTxItem::new_system_tx( + Box::new(tx), + (receipt, Some(address)), + logs_bloom, + )); + + // Point proxy to DST20_v3 + let storage = vec![( + IMPLEMENTATION_SLOT, + h160_to_h256(get_dst20_v3_contract().fixed_address), + )]; + + executor.update_storage(address, storage)?; + } + template.backend.increase_tx_count(); Ok(()) } diff --git a/src/ffi/ffiexports.cpp b/src/ffi/ffiexports.cpp index d88033cad61..76c7cfdb697 100644 --- a/src/ffi/ffiexports.cpp +++ b/src/ffi/ffiexports.cpp @@ -522,6 +522,10 @@ uint64_t getDF23Height() { return Params().GetConsensus().DF23Height; } +uint64_t getDF24Height() { + return Params().GetConsensus().DF24Height; +} + bool migrateTokensFromEVM(std::size_t mnview_ptr, TokenAmount old_amount, TokenAmount &new_amount) { return ExecuteTokenMigrationEVM(mnview_ptr, old_amount, new_amount); } diff --git a/src/ffi/ffiexports.h b/src/ffi/ffiexports.h index 7199403051a..ad2420b7fab 100644 --- a/src/ffi/ffiexports.h +++ b/src/ffi/ffiexports.h @@ -127,6 +127,7 @@ bool isEthDebugTraceRPCEnabled(); // Gets all EVM system txs and their respective types from DVM block. rust::vec getEVMSystemTxsFromBlock(std::array evmBlockHash); uint64_t getDF23Height(); +uint64_t getDF24Height(); bool migrateTokensFromEVM(std::size_t mnview_ptr, TokenAmount old_amount, TokenAmount &new_amount); #endif // DEFI_FFI_FFIEXPORTS_H diff --git a/test/functional/feature_evm_token_split.py b/test/functional/feature_evm_token_split.py index 3e79397d42d..9871e959c1e 100755 --- a/test/functional/feature_evm_token_split.py +++ b/test/functional/feature_evm_token_split.py @@ -36,6 +36,7 @@ def set_test_params(self): "-grandcentralheight=1", "-metachainheight=105", "-df23height=150", + "-df24height=150", ], ] @@ -53,6 +54,9 @@ def run_test(self): # Split token multiple times via transfer domain self.transfer_domain_multiple_split() + # Split tokens 1:1 via v3 intrinsics contract + self.intrinsic_token_split(20, 1, True) + # Split tokens via intrinsics contract self.intrinsic_token_split(20, 2) @@ -171,6 +175,12 @@ def setup_variables(self): encoding="utf8", ).read() + self.dst20_v3_abi = open( + get_solc_artifact_path("dst20_v3", "abi.json"), + "r", + encoding="utf8", + ).read() + # Check META variables self.meta_contract = self.nodes[0].w3.eth.contract( address=self.contract_address_metav1, abi=self.dst20_v2_abi @@ -471,7 +481,7 @@ def transfer_domain_multiple_split(self): Decimal(4000.00000000), ) - def intrinsic_token_split(self, amount, split_multiplier): + def intrinsic_token_split(self, amount, split_multiplier, use_v3=False): # Rollback self.rollback_to(self.block_height) @@ -499,6 +509,7 @@ def intrinsic_token_split(self, amount, split_multiplier): self.contract_address_metav2, amount, split_multiplier, + use_v3, ) # Get values from after transfer out @@ -537,8 +548,9 @@ def intrinsic_token_split(self, amount, split_multiplier): + (amount * decimal_multiplier), ) + # Check already updated token cannot be updated again self.execute_split_transaction_at_highest_level( - self.contract_address_metav2, amount + self.contract_address_metav2, amount, use_v3 ) def intrinsic_token_merge(self, amount, split_multiplier): @@ -703,7 +715,12 @@ def split_token( ) def execute_split_transaction( - self, source_contract, destination_contract, amount=20, split_multiplier=2 + self, + source_contract, + destination_contract, + amount=20, + split_multiplier=2, + use_v3=False, ): # Create the amount to approve @@ -722,9 +739,14 @@ def execute_split_transaction( amount_to_receive = 0 # Get old contract - meta_contract = self.nodes[0].w3.eth.contract( - address=source_contract, abi=self.dst20_v2_abi - ) + if use_v3: + meta_contract = self.nodes[0].w3.eth.contract( + address=source_contract, abi=self.dst20_v3_abi + ) + else: + meta_contract = self.nodes[0].w3.eth.contract( + address=source_contract, abi=self.dst20_v2_abi + ) totalSupplyBefore = meta_contract.functions.totalSupply().call() balance_before = meta_contract.functions.balanceOf(self.evm_address).call() @@ -769,9 +791,14 @@ def execute_split_transaction( assert_equal(totalSupplyAfter, totalSupplyBefore - amount_to_send) # Get new contract - meta_contract_new = self.nodes[0].w3.eth.contract( - address=destination_contract, abi=self.dst20_v2_abi - ) + if use_v3: + meta_contract_new = self.nodes[0].w3.eth.contract( + address=destination_contract, abi=self.dst20_v3_abi + ) + else: + meta_contract_new = self.nodes[0].w3.eth.contract( + address=destination_contract, abi=self.dst20_v2_abi + ) # Check transfer from sender to burn address events = meta_contract_new.events.Transfer().process_log( @@ -798,10 +825,17 @@ def execute_split_transaction( return amount_to_receive - def execute_split_transaction_at_highest_level(self, source_contract, amount=20): - meta_contract = self.nodes[0].w3.eth.contract( - address=source_contract, abi=self.dst20_v2_abi - ) + def execute_split_transaction_at_highest_level( + self, source_contract, amount=20, use_v3=False + ): + if use_v3: + meta_contract = self.nodes[0].w3.eth.contract( + address=source_contract, abi=self.dst20_v3_abi + ) + else: + meta_contract = self.nodes[0].w3.eth.contract( + address=source_contract, abi=self.dst20_v2_abi + ) amount_to_send = Web3.to_wei(amount, "ether") diff --git a/test/functional/feature_restart_interest.py b/test/functional/feature_restart_interest.py index 52d164d24fe..fe3f9cdaa15 100755 --- a/test/functional/feature_restart_interest.py +++ b/test/functional/feature_restart_interest.py @@ -388,7 +388,6 @@ def vault_liquidation(self): # Check vault result = self.nodes[0].getvault(vault_id) - print("After vault", result) assert_equal(result["loanAmounts"], []) assert_equal( result["collateralAmounts"], [f"149.99933408@{self.symbolDFI}"] diff --git a/test/functional/feature_restartdtokens.py b/test/functional/feature_restartdtokens.py index fbc7fdf23ba..75a0716b6d5 100755 --- a/test/functional/feature_restartdtokens.py +++ b/test/functional/feature_restartdtokens.py @@ -612,6 +612,47 @@ def release_final_1(self): [], ) + # check that upgrade token now works + assert_equal( + self.dusd_contract.functions.balanceOf(self.evmaddress).call() / (10**18), + Decimal(19.99999999), + ) + assert_equal( + self.usdd_contract.functions.balanceOf(self.evmaddress).call(), + Decimal(0), + ) + + amount = Web3.to_wei(10, "ether") + + upgrade_txn = self.dusd_contract.functions.upgradeToken( + amount + ).build_transaction( + { + "from": self.evmaddress, + "nonce": self.nodes[0].eth_getTransactionCount(self.evmaddress), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 1_500_000_000, + "gas": 5_000_000, + } + ) + signed_txn = self.nodes[0].w3.eth.account.sign_transaction( + upgrade_txn, self.evm_privkey + ) + + self.nodes[0].w3.eth.send_raw_transaction(signed_txn.rawTransaction) + self.nodes[0].generate(1) + tx_receipt = self.nodes[0].w3.eth.wait_for_transaction_receipt(signed_txn.hash) + assert_equal(tx_receipt["status"], 1) + + assert_equal( + self.dusd_contract.functions.balanceOf(self.evmaddress).call() / (10**18), + Decimal(9.99999999), + ) + assert_equal( + self.usdd_contract.functions.balanceOf(self.evmaddress).call() / (10**18), + Decimal(10), + ) + def check_token_split(self): # updated SPY self.idSPY = list(self.nodes[0].gettoken("SPY").keys())[0] @@ -1045,7 +1086,7 @@ def check_token_lock(self): address=self.nodes[0].w3.to_checksum_address( f"0xff0000000000000000000000000000000000{self.usddId:0{4}x}" ), - abi=self.dst20_v2_abi, + abi=self.dst20_v3_abi, ) assert_equal( [ @@ -1974,22 +2015,22 @@ def setup_variables(self): ) # DST20 ABI - self.dst20_v2_abi = open( - get_solc_artifact_path("dst20_v2", "abi.json"), + self.dst20_v3_abi = open( + get_solc_artifact_path("dst20_v3", "abi.json"), "r", encoding="utf8", ).read() # Check DUSD variables self.dusd_contract = self.nodes[0].w3.eth.contract( - address=self.contract_address_dusdv1, abi=self.dst20_v2_abi + address=self.contract_address_dusdv1, abi=self.dst20_v3_abi ) assert_equal(self.dusd_contract.functions.symbol().call(), "DUSD") assert_equal(self.dusd_contract.functions.name().call(), "dUSD") # Check SPY variables self.spy_contract = self.nodes[0].w3.eth.contract( - address=self.contract_address_spyv1, abi=self.dst20_v2_abi + address=self.contract_address_spyv1, abi=self.dst20_v3_abi ) assert_equal(self.spy_contract.functions.symbol().call(), "SPY") assert_equal(self.spy_contract.functions.name().call(), "SP500") From 6ff00a58abaf4dd16f0f0be1a3c789f461f0ae13 Mon Sep 17 00:00:00 2001 From: Peter John Bushnell Date: Fri, 20 Sep 2024 13:06:10 +0100 Subject: [PATCH 2/2] v4.1.8 (#3060) --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index bca3e416ddc..9be90ed8930 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 4) define(_CLIENT_VERSION_MINOR, 1) -define(_CLIENT_VERSION_REVISION, 7) +define(_CLIENT_VERSION_REVISION, 8) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, true)