From d163c54139ebca62d8e9536d71c271b1384d1827 Mon Sep 17 00:00:00 2001 From: Jouzo <15011228+Jouzo@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:37:32 +0100 Subject: [PATCH] EVM: Add support for token split via DST20 v2 contract (#2903) * DST20 v2 * Clippy fix * Get contract address depending on fork height * Remove unused fn * Fix test WARNING: df23 height should not be set before feature/evm activation or it will conflict with contract deployment. * fmt-rs * Rename occurences of token split by upgradeToken * Add test at highest split level * fmt-rs --------- Co-authored-by: Prasanna Loganathar --- lib/ain-contracts/build.rs | 2 +- .../dfi_intrinsics_v2/DFIIntrinsicsV2.sol | 58 -- .../dfi_intrinsics_v2/IDFIIntrinsicsV2.sol | 11 - lib/ain-contracts/dst20_v2/DST20V2.sol | 604 ++++++++++++++++++ .../dst20_v2/IDST20Upgradeable.sol | 11 + lib/ain-contracts/src/lib.rs | 69 +- lib/ain-evm/src/contract.rs | 482 -------------- lib/ain-evm/src/contract/dst20.rs | 259 ++++++++ lib/ain-evm/src/contract/mod.rs | 247 +++++++ lib/ain-evm/src/evm.rs | 44 +- lib/ain-evm/src/executor.rs | 13 +- lib/ain-evm/src/precompiles/mod.rs | 11 +- lib/ain-evm/src/precompiles/token_split.rs | 42 +- src/dfi/validation.cpp | 3 +- src/ffi/ffiexports.cpp | 5 +- test/functional/feature_evm_rpc_accesslist.py | 31 +- test/functional/feature_evm_token_split.py | 149 +++-- 17 files changed, 1302 insertions(+), 739 deletions(-) delete mode 100644 lib/ain-contracts/dfi_intrinsics_v2/DFIIntrinsicsV2.sol delete mode 100644 lib/ain-contracts/dfi_intrinsics_v2/IDFIIntrinsicsV2.sol create mode 100644 lib/ain-contracts/dst20_v2/DST20V2.sol create mode 100644 lib/ain-contracts/dst20_v2/IDST20Upgradeable.sol delete mode 100644 lib/ain-evm/src/contract.rs create mode 100644 lib/ain-evm/src/contract/dst20.rs create mode 100644 lib/ain-evm/src/contract/mod.rs diff --git a/lib/ain-contracts/build.rs b/lib/ain-contracts/build.rs index db888d45e9..7bfcc42cc9 100644 --- a/lib/ain-contracts/build.rs +++ b/lib/ain-contracts/build.rs @@ -17,11 +17,11 @@ fn main() -> Result<()> { ("dfi_reserved", "DFIReserved"), ("dfi_intrinsics_registry", "DFIIntrinsicsRegistry"), ("dfi_intrinsics_v1", "DFIIntrinsicsV1"), - ("dfi_intrinsics_v2", "DFIIntrinsicsV2"), ("transfer_domain", "TransferDomain"), ("transfer_domain_v1", "TransferDomainV1"), ("dst20", "DST20"), ("dst20_v1", "DST20V1"), + ("dst20_v2", "DST20V2"), ]; for (sol_project_name, contract_name) in contracts { diff --git a/lib/ain-contracts/dfi_intrinsics_v2/DFIIntrinsicsV2.sol b/lib/ain-contracts/dfi_intrinsics_v2/DFIIntrinsicsV2.sol deleted file mode 100644 index 032d843a27..0000000000 --- a/lib/ain-contracts/dfi_intrinsics_v2/DFIIntrinsicsV2.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./IDFIIntrinsicsV2.sol"; - -interface IDFIIntrinsicsRegistry { - function get(uint256 _version) external view returns (address); -} - -contract DFIIntrinsicsV2 is IDFIIntrinsicsV2 { - uint256 private _version; - address private registryAddress; - - function _getRegistry() internal view returns(IDFIIntrinsicsRegistry) { - return IDFIIntrinsicsRegistry(registryAddress); - } - - function _getDFIIntrinsicsV1() internal view returns(IDFIIntrinsicsV1) { - address v1Address = _getRegistry().get(0); - return IDFIIntrinsicsV1(v1Address); - } - - function version() public view override returns (uint256) { - return _version; - } - - function evmBlockCount() public view override returns (uint256) { - return _getDFIIntrinsicsV1().evmBlockCount(); - } - - function dvmBlockCount() public view override returns (uint256) { - return _getDFIIntrinsicsV1().dvmBlockCount(); - } - - function migrateTokens(address tokenContract, uint256 amount) public { - migrateTokensOnAddress(msg.sender, tokenContract, amount); - } - - function migrateTokensOnAddress(address sender, address tokenContract, uint256 amount) private { - address precompileAddress = address(0x0a); - bytes memory inputData = abi.encode(sender, tokenContract, amount); - bytes memory outputData = ""; - bool success; - - assembly { - success := call( - gas(), - precompileAddress, - 0, - add(inputData, 32), - mload(inputData), - add(outputData, 32), - mload(outputData) - ) - } - require(success, "Precompile call failed"); - } -} diff --git a/lib/ain-contracts/dfi_intrinsics_v2/IDFIIntrinsicsV2.sol b/lib/ain-contracts/dfi_intrinsics_v2/IDFIIntrinsicsV2.sol deleted file mode 100644 index 37444ae722..0000000000 --- a/lib/ain-contracts/dfi_intrinsics_v2/IDFIIntrinsicsV2.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IDFIIntrinsicsV1 { - function version() external view returns (uint256); - function evmBlockCount() external view returns(uint256); - function dvmBlockCount() external view returns(uint256); -} - -interface IDFIIntrinsicsV2 is IDFIIntrinsicsV1 { -} diff --git a/lib/ain-contracts/dst20_v2/DST20V2.sol b/lib/ain-contracts/dst20_v2/DST20V2.sol new file mode 100644 index 0000000000..bd35d5ed6d --- /dev/null +++ b/lib/ain-contracts/dst20_v2/DST20V2.sol @@ -0,0 +1,604 @@ +// 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 (newAmount != amount) { + IERC20(newTokenContractAddress).transfer(msg.sender, newAmount); + } + + return (newTokenContractAddress, newAmount); + } +} + +// File: oz.sol + +pragma solidity ^0.8.0; + +contract DST20V2 is ERC20 { + constructor(string memory name, string memory symbol) ERC20(name, symbol) {} +} diff --git a/lib/ain-contracts/dst20_v2/IDST20Upgradeable.sol b/lib/ain-contracts/dst20_v2/IDST20Upgradeable.sol new file mode 100644 index 0000000000..b5114572ed --- /dev/null +++ b/lib/ain-contracts/dst20_v2/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 d2729000ba..703a7b11db 100644 --- a/lib/ain-contracts/src/lib.rs +++ b/lib/ain-contracts/src/lib.rs @@ -6,7 +6,7 @@ use sp_core::{Blake2Hasher, Hasher}; pub type Result = std::result::Result; -const DST20_ADDR_PREFIX_BYTE: u8 = 0xff; +pub const DST20_ADDR_PREFIX_BYTE: u8 = 0xff; const INTRINSICS_ADDR_PREFIX_BYTE: u8 = 0xdf; // Impl slots used for proxies: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc @@ -57,30 +57,6 @@ fn get_bytecode(input: &str) -> Result> { hex::decode(&bytecode_raw[2..]).map_err(|e| format_err!(e.to_string())) } -pub fn get_dst20_deploy_input(init_bytecode: Vec, name: &str, symbol: &str) -> Result> { - let name = ethabi::Token::String(name.to_string()); - let symbol = ethabi::Token::String(symbol.to_string()); - - let constructor = ethabi::Constructor { - inputs: vec![ - ethabi::Param { - name: String::from("name"), - kind: ethabi::ParamType::String, - internal_type: None, - }, - ethabi::Param { - name: String::from("symbol"), - kind: ethabi::ParamType::String, - internal_type: None, - }, - ], - }; - - constructor - .encode_input(init_bytecode, &[name, symbol]) - .map_err(|e| format_err!(e)) -} - pub fn generate_intrinsic_addr(prefix_byte: u8, suffix_num: u64) -> Result { let s = format!("{prefix_byte:x}{suffix_num:0>38x}"); Ok(H160::from_str(&s)?) @@ -155,23 +131,6 @@ lazy_static::lazy_static! { } }; - pub static ref DFI_INTRINSICS_V2_CONTRACT: FixedContract = { - let bytecode = solc_artifact_bytecode_str!("dfi_intrinsics_v2", "deployed_bytecode.json"); - let input = solc_artifact_bytecode_str!( - "dfi_intrinsics_v2", - "bytecode.json" - ); - - FixedContract { - contract: Contract { - codehash: Blake2Hasher::hash(&bytecode), - runtime_bytecode: bytecode, - init_bytecode: input, - }, - fixed_address: H160(slice_20b!(INTRINSICS_ADDR_PREFIX_BYTE, 0x5)), - } - }; - pub static ref TRANSFERDOMAIN_CONTRACT : FixedContract = { let bytecode = solc_artifact_bytecode_str!("transfer_domain", "deployed_bytecode.json"); let input = solc_artifact_bytecode_str!( @@ -236,6 +195,24 @@ lazy_static::lazy_static! { fixed_address: H160(slice_20b!(INTRINSICS_ADDR_PREFIX_BYTE, 0x4)) } }; + + pub static ref DST20_V2_CONTRACT: FixedContract = { + let bytecode = solc_artifact_bytecode_str!( + "dst20_v2", "deployed_bytecode.json" + ); + let input = solc_artifact_bytecode_str!( + "dst20_v2", "bytecode.json" + ); + + FixedContract { + contract: Contract { + codehash: Blake2Hasher::hash(&bytecode), + runtime_bytecode: bytecode, + init_bytecode: input, + }, + fixed_address: H160(slice_20b!(INTRINSICS_ADDR_PREFIX_BYTE, 0x5)) + } + }; } pub fn get_split_tokens_function() -> ethabi::Function { @@ -373,10 +350,6 @@ pub fn get_dfi_intrinsics_v1_contract() -> FixedContract { DFI_INTRINSICS_V1_CONTRACT.clone() } -pub fn get_dfi_intrinsics_v2_contract() -> FixedContract { - DFI_INTRINSICS_V2_CONTRACT.clone() -} - pub fn get_transfer_domain_contract() -> FixedContract { TRANSFERDOMAIN_CONTRACT.clone() } @@ -393,6 +366,10 @@ pub fn get_dst20_v1_contract() -> FixedContract { DST20_V1_CONTRACT.clone() } +pub fn get_dst20_v2_contract() -> FixedContract { + DST20_V2_CONTRACT.clone() +} + #[cfg(test)] mod test { use super::*; diff --git a/lib/ain-evm/src/contract.rs b/lib/ain-evm/src/contract.rs deleted file mode 100644 index 7b6352d541..0000000000 --- a/lib/ain-evm/src/contract.rs +++ /dev/null @@ -1,482 +0,0 @@ -use ain_contracts::{ - get_dfi_instrinics_registry_contract, get_dfi_intrinsics_v1_contract, - get_dfi_intrinsics_v2_contract, get_dfi_reserved_contract, get_dst20_contract, - get_dst20_v1_contract, get_transfer_domain_contract, get_transfer_domain_v1_contract, Contract, - FixedContract, IMPLEMENTATION_SLOT, -}; -use anyhow::format_err; -use ethbloom::Bloom; -use ethereum::{ - EIP1559ReceiptData, LegacyTransaction, ReceiptV3, TransactionAction, TransactionSignature, - TransactionV2, -}; -use ethereum_types::{H160, H256, U256}; -use log::trace; -use sha3::{Digest, Keccak256}; - -use crate::{ - backend::EVMBackend, - bytes::Bytes, - executor::{AinExecutor, ExecuteTx}, - transaction::{ - system::{DeployContractData, SystemTx, TransferDirection}, - SignedTx, LOWER_H256, - }, - Result, -}; - -pub fn u256_to_h256(input: U256) -> H256 { - let mut bytes = [0_u8; 32]; - input.to_big_endian(&mut bytes); - - H256::from(bytes) -} - -pub fn h160_to_h256(h160: H160) -> H256 { - let mut h256 = H256::zero(); - h256.0[12..].copy_from_slice(&h160.0); - h256 -} - -pub fn get_abi_encoded_string(input: &str) -> H256 { - let length = input.len(); - - let mut storage_value = H256::default(); - storage_value.0[31] = (length * 2) as u8; // safe due to character limits on token name/symbol on DVM - storage_value.0[..length].copy_from_slice(input.as_bytes()); - - storage_value -} - -pub fn get_address_storage_index(slot: H256, address: H160) -> H256 { - // padded key - let key = H256::from(address); - - // keccak256(padded key + padded slot) - let mut hasher = Keccak256::new(); - hasher.update(key.as_fixed_bytes()); - hasher.update(slot.as_fixed_bytes()); - let hash_result = hasher.finalize(); - - let mut index_bytes = [0u8; 32]; - index_bytes.copy_from_slice(&hash_result); - - H256::from(index_bytes) -} - -pub fn get_uint_storage_index(slot: H256, num: u64) -> H256 { - // padded key - let key = H256::from_low_u64_be(num); - - // keccak256(padded key + padded slot) - let mut hasher = Keccak256::new(); - hasher.update(key.as_fixed_bytes()); - hasher.update(slot.as_fixed_bytes()); - let hash_result = hasher.finalize(); - - let mut index_bytes = [0u8; 32]; - index_bytes.copy_from_slice(&hash_result); - - H256::from(index_bytes) -} - -pub struct DeployContractInfo { - pub address: H160, - pub storage: Vec<(H256, H256)>, - pub bytecode: Bytes, -} - -pub struct DST20BridgeInfo { - pub address: H160, - pub storage: Vec<(H256, H256)>, -} - -pub fn dfi_intrinsics_registry_deploy_info(addresses: Vec) -> DeployContractInfo { - let FixedContract { - contract, - fixed_address, - } = get_dfi_instrinics_registry_contract(); - - let storage = addresses - .into_iter() - .enumerate() - .map(|(index, address)| { - ( - get_uint_storage_index(H256::from_low_u64_be(0), index as u64), - h160_to_h256(address), - ) - }) - .collect(); - - DeployContractInfo { - address: fixed_address, - bytecode: Bytes::from(contract.runtime_bytecode), - storage, - } -} -/// Returns address, bytecode and storage with incremented count for the counter contract -pub fn dfi_intrinsics_v1_deploy_info( - dvm_block_number: u64, - evm_block_number: U256, -) -> Result { - let FixedContract { - contract, - fixed_address, - .. - } = get_dfi_intrinsics_v1_contract(); - - Ok(DeployContractInfo { - address: fixed_address, - bytecode: Bytes::from(contract.runtime_bytecode), - storage: vec![ - (H256::from_low_u64_be(0), u256_to_h256(U256::one())), - (H256::from_low_u64_be(1), u256_to_h256(evm_block_number)), - ( - H256::from_low_u64_be(2), - u256_to_h256(U256::from(dvm_block_number)), - ), - ], - }) -} - -pub fn dfi_intrinsics_v2_deploy_info(registry_address: H160) -> Result { - let FixedContract { - contract, - fixed_address, - .. - } = get_dfi_intrinsics_v2_contract(); - - Ok(DeployContractInfo { - address: fixed_address, - bytecode: Bytes::from(contract.runtime_bytecode), - storage: vec![ - (H256::from_low_u64_be(0), u256_to_h256(U256::from(2))), - (H256::from_low_u64_be(1), h160_to_h256(registry_address)), - ], - }) -} - -pub fn transfer_domain_deploy_info(implementation_address: H160) -> Result { - let FixedContract { - contract, - fixed_address, - .. - } = get_transfer_domain_contract(); - - Ok(DeployContractInfo { - address: fixed_address, - bytecode: Bytes::from(contract.runtime_bytecode), - storage: vec![(IMPLEMENTATION_SLOT, h160_to_h256(implementation_address))], - }) -} - -/// Returns transfer domain address, bytecode and null storage -pub fn transfer_domain_v1_contract_deploy_info() -> DeployContractInfo { - let FixedContract { - contract, - fixed_address, - } = get_transfer_domain_v1_contract(); - - DeployContractInfo { - address: fixed_address, - bytecode: Bytes::from(contract.runtime_bytecode), - storage: Vec::new(), - } -} - -pub fn dst20_deploy_info( - backend: &EVMBackend, - address: H160, - name: &str, - symbol: &str, -) -> Result { - // TODO: Move checks outside of the deploy_info - match backend.get_account(&address) { - None => {} - Some(account) => { - let Contract { codehash, .. } = get_dfi_reserved_contract(); - if account.code_hash != codehash { - return Err(format_err!("Token {symbol} address is already in use").into()); - } - } - } - - let Contract { - runtime_bytecode, .. - } = get_dst20_contract(); - let storage = dst20_name_info(name, symbol); - - Ok(DeployContractInfo { - address, - bytecode: Bytes::from(runtime_bytecode), - storage, - }) -} - -pub fn dst20_v1_deploy_info() -> DeployContractInfo { - let FixedContract { - contract, - fixed_address, - } = get_dst20_v1_contract(); - - DeployContractInfo { - address: fixed_address, - bytecode: Bytes::from(contract.runtime_bytecode), - storage: Vec::new(), - } -} - -pub fn bridge_dst20_in( - backend: &EVMBackend, - contract: H160, - amount: U256, -) -> Result { - // check if code of address matches DST20 bytecode - let account = backend - .get_account(&contract) - .ok_or_else(|| format_err!("DST20 token address is not a contract"))?; - - let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); - - let Contract { codehash, .. } = get_dst20_contract(); - if account.code_hash != codehash { - return Err(format_err!("DST20 token code is not valid").into()); - } - - // balance has slot 0 - let contract_balance_storage_index = get_address_storage_index(H256::zero(), fixed_address); - - let total_supply_index = H256::from_low_u64_be(2); - let total_supply = backend.get_contract_storage(contract, total_supply_index)?; - - let new_total_supply = total_supply - .checked_add(amount) - .ok_or_else(|| format_err!("Total supply overflow/underflow"))?; - - Ok(DST20BridgeInfo { - address: contract, - storage: vec![ - (contract_balance_storage_index, u256_to_h256(amount)), - (total_supply_index, u256_to_h256(new_total_supply)), - ], - }) -} - -pub fn bridge_dst20_out( - backend: &EVMBackend, - contract: H160, - amount: U256, -) -> Result { - // check if code of address matches DST20 bytecode - let account = backend - .get_account(&contract) - .ok_or_else(|| format_err!("DST20 token address is not a contract"))?; - - let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); - - let Contract { codehash, .. } = get_dst20_contract(); - if account.code_hash != codehash { - return Err(format_err!("DST20 token code is not valid").into()); - } - - let contract_balance_storage_index = get_address_storage_index(H256::zero(), fixed_address); - - let total_supply_index = H256::from_low_u64_be(2); - let total_supply = backend.get_contract_storage(contract, total_supply_index)?; - - let new_total_supply = total_supply - .checked_sub(amount) - .ok_or_else(|| format_err!("Total supply overflow/underflow"))?; - - Ok(DST20BridgeInfo { - address: contract, - storage: vec![ - (contract_balance_storage_index, u256_to_h256(U256::zero())), // Reset contract balance to 0 - (total_supply_index, u256_to_h256(new_total_supply)), - ], - }) -} - -pub fn dst20_allowance( - direction: TransferDirection, - from: H160, - amount: U256, -) -> Vec<(H256, H256)> { - let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); - - // allowance has slot 1 - let allowance_storage_index = get_address_storage_index( - H256::from_low_u64_be(1), - if direction == TransferDirection::EvmIn { - fixed_address - } else { - from - }, - ); - let address_allowance_storage_index = - get_address_storage_index(allowance_storage_index, fixed_address); - - vec![(address_allowance_storage_index, u256_to_h256(amount))] -} - -pub fn bridge_dfi( - backend: &EVMBackend, - amount: U256, - direction: TransferDirection, -) -> Result> { - let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); - - let total_supply_index = H256::from_low_u64_be(1); - let total_supply = backend.get_contract_storage(fixed_address, total_supply_index)?; - - let new_total_supply = if direction == TransferDirection::EvmOut { - total_supply.checked_sub(amount) - } else { - total_supply.checked_add(amount) - }; - - let new_total_supply = - new_total_supply.ok_or_else(|| format_err!("Total supply overflow/underflow"))?; - - Ok(vec![(total_supply_index, u256_to_h256(new_total_supply))]) -} - -pub fn reserve_dst20_namespace(executor: &mut AinExecutor) -> Result<()> { - let Contract { - runtime_bytecode, .. - } = get_dfi_reserved_contract(); - let addresses = (0..1024) - .map(|token_id| ain_contracts::dst20_address_from_token_id(token_id).unwrap()) - .collect::>(); - - for address in addresses { - trace!( - "[reserve_dst20_namespace] Deploying address to {:#?}", - address - ); - executor.deploy_contract(address, runtime_bytecode.clone().into(), Vec::new())?; - } - - Ok(()) -} - -pub fn reserve_intrinsics_namespace(executor: &mut AinExecutor) -> Result<()> { - let Contract { - runtime_bytecode, .. - } = get_dfi_reserved_contract(); - let addresses = (0..128) - .map(|token_id| ain_contracts::intrinsics_address_from_id(token_id).unwrap()) - .collect::>(); - - for address in addresses { - trace!( - "[reserve_intrinsics_namespace] Deploying address to {:#?}", - address - ); - executor.deploy_contract(address, runtime_bytecode.clone().into(), Vec::new())?; - } - - Ok(()) -} - -pub fn dst20_deploy_contract_tx( - token_id: u64, - base_fee: &U256, -) -> Result<(Box, ReceiptV3)> { - let tx = TransactionV2::Legacy(LegacyTransaction { - nonce: U256::from(token_id), - gas_price: *base_fee, - gas_limit: U256::from(u64::MAX), - action: TransactionAction::Create, - value: U256::zero(), - input: get_transfer_domain_contract().contract.init_bytecode, - signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) - .ok_or(format_err!("Invalid transaction signature format"))?, - }) - .try_into()?; - - let receipt = get_default_successful_receipt(); - - Ok((Box::new(tx), receipt)) -} - -pub fn deploy_contract_tx(bytecode: Vec, base_fee: &U256) -> Result<(SignedTx, ReceiptV3)> { - let tx = TransactionV2::Legacy(LegacyTransaction { - nonce: U256::zero(), - gas_price: *base_fee, - gas_limit: U256::from(u64::MAX), - action: TransactionAction::Create, - value: U256::zero(), - input: bytecode, - signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) - .ok_or(format_err!("Invalid transaction signature format"))?, - }) - .try_into()?; - - let receipt = get_default_successful_receipt(); - - Ok((tx, receipt)) -} - -pub fn rename_contract_tx(token_id: u64, base_fee: &U256) -> Result<(Box, ReceiptV3)> { - let tx = TransactionV2::Legacy(LegacyTransaction { - nonce: U256::from(token_id), - gas_price: *base_fee, - gas_limit: U256::from(u64::MAX), - action: TransactionAction::Create, - value: U256::zero(), - input: Vec::new(), - signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) - .ok_or(format_err!("Invalid transaction signature format"))?, - }) - .try_into()?; - - let receipt = get_default_successful_receipt(); - - Ok((Box::new(tx), receipt)) -} - -fn get_default_successful_receipt() -> ReceiptV3 { - ReceiptV3::Legacy(EIP1559ReceiptData { - status_code: 1u8, - used_gas: U256::zero(), - logs_bloom: Bloom::default(), - logs: Vec::new(), - }) -} - -pub fn get_dst20_migration_txs(mnview_ptr: usize) -> Result> { - let mut tokens = vec![]; - let mut txs = Vec::new(); - if !ain_cpp_imports::get_dst20_tokens(mnview_ptr, &mut tokens) { - return Err(format_err!("DST20 token migration failed, invalid token name.").into()); - } - - for token in tokens { - let address = ain_contracts::dst20_address_from_token_id(token.id)?; - trace!( - "[get_dst20_migration_txs] Deploying to address {:#?}", - address - ); - - let tx = ExecuteTx::SystemTx(SystemTx::DeployContract(DeployContractData { - name: token.name, - symbol: token.symbol, - token_id: token.id, - address, - })); - txs.push(tx); - } - Ok(txs) -} - -pub fn dst20_name_info(name: &str, symbol: &str) -> Vec<(H256, H256)> { - vec![ - (H256::from_low_u64_be(3), get_abi_encoded_string(name)), - (H256::from_low_u64_be(4), get_abi_encoded_string(symbol)), - ( - IMPLEMENTATION_SLOT, - h160_to_h256(get_dst20_v1_contract().fixed_address), - ), - ] -} diff --git a/lib/ain-evm/src/contract/dst20.rs b/lib/ain-evm/src/contract/dst20.rs new file mode 100644 index 0000000000..1a34dc24a4 --- /dev/null +++ b/lib/ain-evm/src/contract/dst20.rs @@ -0,0 +1,259 @@ +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, +}; +use anyhow::format_err; +use ethereum::{ + LegacyTransaction, ReceiptV3, TransactionAction, TransactionSignature, TransactionV2, +}; +use ethereum_types::{H160, H256, U256}; +use log::trace; + +use crate::{ + backend::EVMBackend, + bytes::Bytes, + executor::{AinExecutor, ExecuteTx}, + transaction::{ + system::{DeployContractData, SystemTx, TransferDirection}, + SignedTx, LOWER_H256, + }, + Result, +}; + +use super::{ + get_abi_encoded_string, get_address_storage_index, get_default_successful_receipt, + h160_to_h256, u256_to_h256, DeployContractInfo, +}; + +pub struct DST20BridgeInfo { + pub address: H160, + pub storage: Vec<(H256, H256)>, +} + +pub fn dst20_deploy_info( + backend: &EVMBackend, + dvm_block: u64, + address: H160, + name: &str, + symbol: &str, +) -> Result { + // TODO: Move checks outside of the deploy_info + match backend.get_account(&address) { + None => {} + Some(account) => { + let Contract { codehash, .. } = get_dfi_reserved_contract(); + if account.code_hash != codehash { + return Err(format_err!("Token {symbol} address is already in use").into()); + } + } + } + + let Contract { + runtime_bytecode, .. + } = get_dst20_contract(); + let storage = dst20_name_info(dvm_block, name, symbol); + + Ok(DeployContractInfo { + address, + bytecode: Bytes::from(runtime_bytecode), + storage, + }) +} + +pub fn dst20_v1_deploy_info() -> DeployContractInfo { + let FixedContract { + contract, + fixed_address, + } = get_dst20_v1_contract(); + + DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage: Vec::new(), + } +} + +pub fn dst20_v2_deploy_info() -> DeployContractInfo { + let FixedContract { + contract, + fixed_address, + } = get_dst20_v2_contract(); + + DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage: Vec::new(), + } +} + +pub fn bridge_dst20_in( + backend: &EVMBackend, + contract: H160, + amount: U256, +) -> Result { + // check if code of address matches DST20 bytecode + let account = backend + .get_account(&contract) + .ok_or_else(|| format_err!("DST20 token address is not a contract"))?; + + let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); + + let Contract { codehash, .. } = get_dst20_contract(); + if account.code_hash != codehash { + return Err(format_err!("DST20 token code is not valid").into()); + } + + // balance has slot 0 + let contract_balance_storage_index = get_address_storage_index(H256::zero(), fixed_address); + + let total_supply_index = H256::from_low_u64_be(2); + let total_supply = backend.get_contract_storage(contract, total_supply_index)?; + + let new_total_supply = total_supply + .checked_add(amount) + .ok_or_else(|| format_err!("Total supply overflow/underflow"))?; + + Ok(DST20BridgeInfo { + address: contract, + storage: vec![ + (contract_balance_storage_index, u256_to_h256(amount)), + (total_supply_index, u256_to_h256(new_total_supply)), + ], + }) +} + +pub fn bridge_dst20_out( + backend: &EVMBackend, + contract: H160, + amount: U256, +) -> Result { + // check if code of address matches DST20 bytecode + let account = backend + .get_account(&contract) + .ok_or_else(|| format_err!("DST20 token address is not a contract"))?; + + let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); + + let Contract { codehash, .. } = get_dst20_contract(); + if account.code_hash != codehash { + return Err(format_err!("DST20 token code is not valid").into()); + } + + let contract_balance_storage_index = get_address_storage_index(H256::zero(), fixed_address); + + let total_supply_index = H256::from_low_u64_be(2); + let total_supply = backend.get_contract_storage(contract, total_supply_index)?; + + let new_total_supply = total_supply + .checked_sub(amount) + .ok_or_else(|| format_err!("Total supply overflow/underflow"))?; + + Ok(DST20BridgeInfo { + address: contract, + storage: vec![ + (contract_balance_storage_index, u256_to_h256(U256::zero())), // Reset contract balance to 0 + (total_supply_index, u256_to_h256(new_total_supply)), + ], + }) +} + +pub fn dst20_allowance( + direction: TransferDirection, + from: H160, + amount: U256, +) -> Vec<(H256, H256)> { + let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); + + // allowance has slot 1 + let allowance_storage_index = get_address_storage_index( + H256::from_low_u64_be(1), + if direction == TransferDirection::EvmIn { + fixed_address + } else { + from + }, + ); + let address_allowance_storage_index = + get_address_storage_index(allowance_storage_index, fixed_address); + + vec![(address_allowance_storage_index, u256_to_h256(amount))] +} + +pub fn reserve_dst20_namespace(executor: &mut AinExecutor) -> Result<()> { + let Contract { + runtime_bytecode, .. + } = get_dfi_reserved_contract(); + let addresses = (0..1024) + .map(|token_id| ain_contracts::dst20_address_from_token_id(token_id).unwrap()) + .collect::>(); + + for address in addresses { + trace!( + "[reserve_dst20_namespace] Deploying address to {:#?}", + address + ); + executor.deploy_contract(address, runtime_bytecode.clone().into(), Vec::new())?; + } + + Ok(()) +} + +pub fn dst20_deploy_contract_tx( + token_id: u64, + base_fee: &U256, +) -> Result<(Box, ReceiptV3)> { + let tx = TransactionV2::Legacy(LegacyTransaction { + nonce: U256::from(token_id), + gas_price: *base_fee, + gas_limit: U256::from(u64::MAX), + action: TransactionAction::Create, + value: U256::zero(), + input: get_transfer_domain_contract().contract.init_bytecode, + signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) + .ok_or(format_err!("Invalid transaction signature format"))?, + }) + .try_into()?; + + let receipt = get_default_successful_receipt(); + + Ok((Box::new(tx), receipt)) +} + +pub fn get_dst20_migration_txs(mnview_ptr: usize) -> Result> { + let mut tokens = vec![]; + let mut txs = Vec::new(); + if !ain_cpp_imports::get_dst20_tokens(mnview_ptr, &mut tokens) { + return Err(format_err!("DST20 token migration failed, invalid token name.").into()); + } + + for token in tokens { + let address = ain_contracts::dst20_address_from_token_id(token.id)?; + trace!( + "[get_dst20_migration_txs] Deploying to address {:#?}", + address + ); + + let tx = ExecuteTx::SystemTx(SystemTx::DeployContract(DeployContractData { + name: token.name, + symbol: token.symbol, + token_id: token.id, + address, + })); + txs.push(tx); + } + Ok(txs) +} + +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() { + get_dst20_v2_contract().fixed_address + } else { + get_dst20_v1_contract().fixed_address + }; + + vec![ + (H256::from_low_u64_be(3), get_abi_encoded_string(name)), + (H256::from_low_u64_be(4), get_abi_encoded_string(symbol)), + (IMPLEMENTATION_SLOT, h160_to_h256(contract_address)), + ] +} diff --git a/lib/ain-evm/src/contract/mod.rs b/lib/ain-evm/src/contract/mod.rs new file mode 100644 index 0000000000..a7aa2c865d --- /dev/null +++ b/lib/ain-evm/src/contract/mod.rs @@ -0,0 +1,247 @@ +pub mod dst20; + +use ain_contracts::{ + get_dfi_instrinics_registry_contract, get_dfi_intrinsics_v1_contract, + get_dfi_reserved_contract, get_transfer_domain_contract, get_transfer_domain_v1_contract, + Contract, FixedContract, IMPLEMENTATION_SLOT, +}; +use anyhow::format_err; +use ethbloom::Bloom; +use ethereum::{ + EIP1559ReceiptData, LegacyTransaction, ReceiptV3, TransactionAction, TransactionSignature, + TransactionV2, +}; +use ethereum_types::{H160, H256, U256}; +use log::trace; +use sha3::{Digest, Keccak256}; + +use crate::{ + backend::EVMBackend, + bytes::Bytes, + executor::AinExecutor, + transaction::{system::TransferDirection, SignedTx, LOWER_H256}, + Result, +}; + +pub fn u256_to_h256(input: U256) -> H256 { + let mut bytes = [0_u8; 32]; + input.to_big_endian(&mut bytes); + + H256::from(bytes) +} + +pub fn h160_to_h256(h160: H160) -> H256 { + let mut h256 = H256::zero(); + h256.0[12..].copy_from_slice(&h160.0); + h256 +} + +pub fn get_abi_encoded_string(input: &str) -> H256 { + let length = input.len(); + + let mut storage_value = H256::default(); + storage_value.0[31] = (length * 2) as u8; // safe due to character limits on token name/symbol on DVM + storage_value.0[..length].copy_from_slice(input.as_bytes()); + + storage_value +} + +pub fn get_address_storage_index(slot: H256, address: H160) -> H256 { + // padded key + let key = H256::from(address); + + // keccak256(padded key + padded slot) + let mut hasher = Keccak256::new(); + hasher.update(key.as_fixed_bytes()); + hasher.update(slot.as_fixed_bytes()); + let hash_result = hasher.finalize(); + + let mut index_bytes = [0u8; 32]; + index_bytes.copy_from_slice(&hash_result); + + H256::from(index_bytes) +} + +pub fn get_uint_storage_index(slot: H256, num: u64) -> H256 { + // padded key + let key = H256::from_low_u64_be(num); + + // keccak256(padded key + padded slot) + let mut hasher = Keccak256::new(); + hasher.update(key.as_fixed_bytes()); + hasher.update(slot.as_fixed_bytes()); + let hash_result = hasher.finalize(); + + let mut index_bytes = [0u8; 32]; + index_bytes.copy_from_slice(&hash_result); + + H256::from(index_bytes) +} + +pub struct DeployContractInfo { + pub address: H160, + pub storage: Vec<(H256, H256)>, + pub bytecode: Bytes, +} + +pub fn dfi_intrinsics_registry_deploy_info(addresses: Vec) -> DeployContractInfo { + let FixedContract { + contract, + fixed_address, + } = get_dfi_instrinics_registry_contract(); + + let storage = addresses + .into_iter() + .enumerate() + .map(|(index, address)| { + ( + get_uint_storage_index(H256::from_low_u64_be(0), index as u64), + h160_to_h256(address), + ) + }) + .collect(); + + DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage, + } +} +/// Returns address, bytecode and storage with incremented count for the counter contract +pub fn dfi_intrinsics_v1_deploy_info( + dvm_block_number: u64, + evm_block_number: U256, +) -> Result { + let FixedContract { + contract, + fixed_address, + .. + } = get_dfi_intrinsics_v1_contract(); + + Ok(DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage: vec![ + (H256::from_low_u64_be(0), u256_to_h256(U256::one())), + (H256::from_low_u64_be(1), u256_to_h256(evm_block_number)), + ( + H256::from_low_u64_be(2), + u256_to_h256(U256::from(dvm_block_number)), + ), + ], + }) +} + +pub fn transfer_domain_deploy_info(implementation_address: H160) -> Result { + let FixedContract { + contract, + fixed_address, + .. + } = get_transfer_domain_contract(); + + Ok(DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage: vec![(IMPLEMENTATION_SLOT, h160_to_h256(implementation_address))], + }) +} + +/// Returns transfer domain address, bytecode and null storage +pub fn transfer_domain_v1_contract_deploy_info() -> DeployContractInfo { + let FixedContract { + contract, + fixed_address, + } = get_transfer_domain_v1_contract(); + + DeployContractInfo { + address: fixed_address, + bytecode: Bytes::from(contract.runtime_bytecode), + storage: Vec::new(), + } +} + +pub fn bridge_dfi( + backend: &EVMBackend, + amount: U256, + direction: TransferDirection, +) -> Result> { + let FixedContract { fixed_address, .. } = get_transfer_domain_contract(); + + let total_supply_index = H256::from_low_u64_be(1); + let total_supply = backend.get_contract_storage(fixed_address, total_supply_index)?; + + let new_total_supply = if direction == TransferDirection::EvmOut { + total_supply.checked_sub(amount) + } else { + total_supply.checked_add(amount) + }; + + let new_total_supply = + new_total_supply.ok_or_else(|| format_err!("Total supply overflow/underflow"))?; + + Ok(vec![(total_supply_index, u256_to_h256(new_total_supply))]) +} + +pub fn reserve_intrinsics_namespace(executor: &mut AinExecutor) -> Result<()> { + let Contract { + runtime_bytecode, .. + } = get_dfi_reserved_contract(); + let addresses = (0..128) + .map(|token_id| ain_contracts::intrinsics_address_from_id(token_id).unwrap()) + .collect::>(); + + for address in addresses { + trace!( + "[reserve_intrinsics_namespace] Deploying address to {:#?}", + address + ); + executor.deploy_contract(address, runtime_bytecode.clone().into(), Vec::new())?; + } + + Ok(()) +} + +pub fn deploy_contract_tx(bytecode: Vec, base_fee: &U256) -> Result<(SignedTx, ReceiptV3)> { + let tx = TransactionV2::Legacy(LegacyTransaction { + nonce: U256::zero(), + gas_price: *base_fee, + gas_limit: U256::from(u64::MAX), + action: TransactionAction::Create, + value: U256::zero(), + input: bytecode, + signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) + .ok_or(format_err!("Invalid transaction signature format"))?, + }) + .try_into()?; + + let receipt = get_default_successful_receipt(); + + Ok((tx, receipt)) +} + +pub fn rename_contract_tx(token_id: u64, base_fee: &U256) -> Result<(Box, ReceiptV3)> { + let tx = TransactionV2::Legacy(LegacyTransaction { + nonce: U256::from(token_id), + gas_price: *base_fee, + gas_limit: U256::from(u64::MAX), + action: TransactionAction::Create, + value: U256::zero(), + input: Vec::new(), + signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) + .ok_or(format_err!("Invalid transaction signature format"))?, + }) + .try_into()?; + + let receipt = get_default_successful_receipt(); + + Ok((Box::new(tx), receipt)) +} + +fn get_default_successful_receipt() -> ReceiptV3 { + ReceiptV3::Legacy(EIP1559ReceiptData { + status_code: 1u8, + used_gas: U256::zero(), + logs_bloom: Bloom::default(), + logs: Vec::new(), + }) +} diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index c919d63bd8..0b5395ca99 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -1,9 +1,9 @@ use std::{path::PathBuf, sync::Arc}; use ain_contracts::{ - get_dfi_instrinics_registry_contract, get_dfi_intrinsics_v1_contract, - get_dfi_intrinsics_v2_contract, get_dst20_v1_contract, get_transfer_domain_contract, - get_transfer_domain_v1_contract, + 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, }; use ain_cpp_imports::{get_df23_height, Attributes}; use anyhow::format_err; @@ -17,8 +17,11 @@ use crate::{ blocktemplate::{BlockData, BlockTemplate, ReceiptAndOptionalContractAddress, TemplateTxItem}, contract::{ deploy_contract_tx, dfi_intrinsics_registry_deploy_info, dfi_intrinsics_v1_deploy_info, - dfi_intrinsics_v2_deploy_info, dst20_v1_deploy_info, get_dst20_migration_txs, - reserve_dst20_namespace, reserve_intrinsics_namespace, transfer_domain_deploy_info, + dst20::{ + dst20_v1_deploy_info, dst20_v2_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, }, core::{EVMCoreService, XHash}, @@ -450,38 +453,31 @@ impl EVMServices { } if is_df23_fork { - // Deploy DFIIntrinsicsRegistry contract - let DeployContractInfo { - address, storage, .. - } = dfi_intrinsics_registry_deploy_info(vec![ - get_dfi_intrinsics_v1_contract().fixed_address, - get_dfi_intrinsics_v2_contract().fixed_address, - ]); - - executor.update_storage(address, storage)?; - - // Deploy DFIIntrinsicsV2 contract + // Deploy token split contract let DeployContractInfo { address, storage, bytecode, - } = dfi_intrinsics_v2_deploy_info( - get_dfi_instrinics_registry_contract().fixed_address, - )?; + } = dst20_v2_deploy_info(); trace!("deploying {:x?} bytecode {:?}", address, bytecode); executor.deploy_contract(address, bytecode, storage)?; - // DFIIntrinsicsV2 contract deployment TX - let (tx, receipt) = deploy_contract_tx( - get_dfi_intrinsics_v2_contract().contract.init_bytecode, - &base_fee, - )?; + let (tx, receipt) = + deploy_contract_tx(get_dst20_v2_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_v2 + let storage = vec![( + IMPLEMENTATION_SLOT, + h160_to_h256(get_dst20_v2_contract().fixed_address), + )]; + + executor.update_storage(address, storage)?; } template.backend.increase_tx_count(); diff --git a/lib/ain-evm/src/executor.rs b/lib/ain-evm/src/executor.rs index c7474031da..54b78db511 100644 --- a/lib/ain-evm/src/executor.rs +++ b/lib/ain-evm/src/executor.rs @@ -16,9 +16,12 @@ use crate::{ blocktemplate::ReceiptAndOptionalContractAddress, bytes::Bytes, contract::{ - bridge_dfi, bridge_dst20_in, bridge_dst20_out, dst20_allowance, dst20_deploy_contract_tx, - dst20_deploy_info, dst20_name_info, rename_contract_tx, DST20BridgeInfo, - DeployContractInfo, + bridge_dfi, + dst20::{ + bridge_dst20_in, bridge_dst20_out, dst20_allowance, dst20_deploy_contract_tx, + dst20_deploy_info, dst20_name_info, DST20BridgeInfo, + }, + rename_contract_tx, DeployContractInfo, }, core::EVMCoreService, eventlistener::{ExecListener, ExecutionStep, GasListener, StorageAccessListener}, @@ -615,7 +618,7 @@ impl<'backend> AinExecutor<'backend> { address, bytecode, storage, - } = dst20_deploy_info(self.backend, address, &name, &symbol)?; + } = dst20_deploy_info(self.backend, ctx.dvm_block, address, &name, &symbol)?; self.deploy_contract(address, bytecode, storage)?; let (tx, receipt) = dst20_deploy_contract_tx(token_id, &base_fee)?; @@ -639,7 +642,7 @@ impl<'backend> AinExecutor<'backend> { address, name, symbol ); - let storage = dst20_name_info(&name, &symbol); + let storage = dst20_name_info(ctx.dvm_block, &name, &symbol); self.update_storage(address, storage)?; let (tx, receipt) = rename_contract_tx(token_id, &base_fee)?; diff --git a/lib/ain-evm/src/precompiles/mod.rs b/lib/ain-evm/src/precompiles/mod.rs index 0fbe480e50..ef04d2c493 100644 --- a/lib/ain-evm/src/precompiles/mod.rs +++ b/lib/ain-evm/src/precompiles/mod.rs @@ -7,7 +7,7 @@ mod token_split; #[cfg(test)] mod test_vector_support; -use ain_contracts::get_dfi_intrinsics_v2_contract; +use ain_contracts::DST20_ADDR_PREFIX_BYTE; use blake2::Blake2F; use bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use ethereum_types::H160; @@ -116,9 +116,6 @@ impl MetachainPrecompiles { impl PrecompileSet for MetachainPrecompiles { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - let is_intrinsics = - || handle.context().caller == get_dfi_intrinsics_v2_contract().fixed_address; - match handle.code_address() { a if a == hash(1) => Some(::execute(handle)), a if a == hash(2) => Some(::execute(handle)), @@ -130,7 +127,7 @@ impl PrecompileSet for MetachainPrecompiles { a if a == hash(8) => Some(Bn128Pairing::execute(handle)), a if a == hash(9) => Some(Blake2F::execute(handle)), - a if a == hash(10) && is_intrinsics() => { + a if a == hash(10) && is_dst20(handle.context().caller) => { let mnview_ptr = self.0.unwrap_or_default(); // If None, should fetch from global view Some(TokenSplit::execute(handle, mnview_ptr)) } @@ -149,3 +146,7 @@ impl PrecompileSet for MetachainPrecompiles { fn hash(a: u64) -> H160 { H160::from_low_u64_be(a) } + +fn is_dst20(addr: H160) -> bool { + matches!(addr.as_fixed_bytes(), [prefix, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ..] if prefix == &DST20_ADDR_PREFIX_BYTE) +} diff --git a/lib/ain-evm/src/precompiles/token_split.rs b/lib/ain-evm/src/precompiles/token_split.rs index 7d77db50d1..3273469a04 100644 --- a/lib/ain-evm/src/precompiles/token_split.rs +++ b/lib/ain-evm/src/precompiles/token_split.rs @@ -2,13 +2,16 @@ use std::collections::BTreeMap; use ain_contracts::{dst20_address_from_token_id, validate_split_tokens_input, TokenSplitParams}; use ain_cpp_imports::{split_tokens_from_evm, TokenAmount}; +use anyhow::format_err; use ethereum_types::{H160, H256, U256}; use evm::{ backend::Apply, executor::stack::{PrecompileFailure, PrecompileHandle, PrecompileOutput}, ExitError, ExitSucceed, }; +use log::debug; +use super::DVMStatePrecompile; use crate::{ contract::{get_address_storage_index, u256_to_h256}, precompiles::PrecompileResult, @@ -16,11 +19,6 @@ use crate::{ Result, }; -use anyhow::format_err; -use log::debug; - -use super::DVMStatePrecompile; - pub struct TokenSplit; impl TokenSplit { @@ -38,6 +36,7 @@ impl DVMStatePrecompile for TokenSplit { sender, token_contract: original_contract, amount: input_amount, + .. } = validate_split_tokens_input(input).map_err(|e| PrecompileFailure::Error { exit_status: ExitError::Other(e.to_string().into()), })?; @@ -59,18 +58,16 @@ impl DVMStatePrecompile for TokenSplit { }); }; - let dvm_id = (contract_value - contract_base).low_u64() as u32; + let old_token_id = (contract_value - contract_base).low_u64() as u32; let old_amount = TokenAmount { - id: dvm_id, + id: old_token_id, amount: amount.low_u64(), }; debug!("[TokenSplit] old_amount : {:?}", old_amount); let mut new_amount = TokenAmount { id: 0, amount: 0 }; - let res = split_tokens_from_evm(mnview_ptr, old_amount, &mut new_amount); - if !res { return Err(PrecompileFailure::Error { exit_status: ExitError::Other("Failed to split tokens".into()), @@ -89,9 +86,28 @@ impl DVMStatePrecompile for TokenSplit { }); }; - let Ok(storage) = - get_new_contract_storage_update(handle, sender, new_contract, converted_amount.0) - else { + let output = { + let mut bytes = [0u8; 64]; + bytes[12..32].copy_from_slice(new_contract.as_bytes()); + converted_amount.0.to_big_endian(&mut bytes[32..]); + bytes.to_vec() + }; + + // No split took place + if new_amount.id == old_token_id { + return Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + state_changes: None, + output, + }); + } + + let Ok(storage) = get_new_contract_storage_update( + handle, + original_contract, + new_contract, + converted_amount.0, + ) else { return Err(PrecompileFailure::Error { exit_status: ExitError::Other("Error getting storage update".into()), }); @@ -127,7 +143,7 @@ impl DVMStatePrecompile for TokenSplit { original_contract_state_changes, new_contract_state_changes, ]), - output: Vec::new(), + output, }) } } diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 05f3555c24..054bdf69f1 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -3217,7 +3217,8 @@ bool ExecuteTokenMigrationEVM(std::size_t mnview_ptr, const TokenAmount oldAmoun const auto idMultiplierPair = cache->GetTokenSplitMultiplier(oldAmount.id); if (!idMultiplierPair) { - return false; + newAmount = oldAmount; + return true; } auto &[id, multiplierVariant] = *idMultiplierPair; diff --git a/src/ffi/ffiexports.cpp b/src/ffi/ffiexports.cpp index 0a3452670f..f71b8998ef 100644 --- a/src/ffi/ffiexports.cpp +++ b/src/ffi/ffiexports.cpp @@ -395,8 +395,5 @@ uint64_t getDF23Height() { } bool migrateTokensFromEVM(std::size_t mnview_ptr, TokenAmount old_amount, TokenAmount &new_amount) { - if (!ExecuteTokenMigrationEVM(mnview_ptr, old_amount, new_amount)) { - return false; - } - return true; + return ExecuteTokenMigrationEVM(mnview_ptr, old_amount, new_amount); } diff --git a/test/functional/feature_evm_rpc_accesslist.py b/test/functional/feature_evm_rpc_accesslist.py index 127474f100..087ea8ec63 100644 --- a/test/functional/feature_evm_rpc_accesslist.py +++ b/test/functional/feature_evm_rpc_accesslist.py @@ -41,7 +41,7 @@ def set_test_params(self): "-fortcanningepilogueheight=96", "-grandcentralheight=101", "-metachainheight=153", - "-df23height=153", + "-df23height=155", "-subsidytest=1", ] ] @@ -88,6 +88,19 @@ def setup(self): # Generate chain self.nodes[0].generate(153) + self.nodes[0].setgov( + { + "ATTRIBUTES": { + "v0/params/feature/evm": "true", + "v0/params/feature/transferdomain": "true", + "v0/transferdomain/dvm-evm/enabled": "true", + "v0/transferdomain/dvm-evm/dat-enabled": "true", + "v0/transferdomain/evm-dvm/dat-enabled": "true", + } + } + ) + self.nodes[0].generate(2) + self.nodes[0].utxostoaccount({self.address: "1000@DFI"}) # Create token before EVM @@ -100,24 +113,13 @@ def setup(self): } ) self.nodes[0].generate(1) + self.nodes[0].minttokens("10@USDT") - self.nodes[0].generate(2) + self.nodes[0].generate(1) self.key_pair = EvmKeyPair.from_node(self.nodes[0]) self.key_pair2 = EvmKeyPair.from_node(self.nodes[0]) - self.nodes[0].setgov( - { - "ATTRIBUTES": { - "v0/params/feature/evm": "true", - "v0/params/feature/transferdomain": "true", - "v0/transferdomain/dvm-evm/enabled": "true", - "v0/transferdomain/dvm-evm/dat-enabled": "true", - "v0/transferdomain/evm-dvm/dat-enabled": "true", - } - } - ) - self.nodes[0].generate(2) self.usdt = self.nodes[0].w3.eth.contract( address=self.contract_address_usdt, abi=self.abi ) @@ -136,6 +138,7 @@ def setup(self): ] ) self.nodes[0].generate(1) + balance = self.usdt.functions.balanceOf( self.key_pair.address ).call() / math.pow(10, self.usdt.functions.decimals().call()) diff --git a/test/functional/feature_evm_token_split.py b/test/functional/feature_evm_token_split.py index 74bd715e16..18cef2fbc2 100755 --- a/test/functional/feature_evm_token_split.py +++ b/test/functional/feature_evm_token_split.py @@ -44,9 +44,6 @@ def run_test(self): # Run setup self.setup() - # Check intrinsics contract backwards compatibility - self.check_backwards_compatibility() - # Store block height for rollback self.block_height = self.nodes[0].getblockcount() @@ -157,20 +154,6 @@ def setup_variables(self): "0xff00000000000000000000000000000000000003" ) - # Registry ABI - registry_abi = open( - get_solc_artifact_path("dfi_intrinsics_registry", "abi.json"), - "r", - encoding="utf8", - ).read() - - # DFI intrinsics V2 ABI - self.intinsics_abi = open( - get_solc_artifact_path("dfi_intrinsics_v2", "abi.json"), - "r", - encoding="utf8", - ).read() - # DST20 ABI self.dst20_abi = open( get_solc_artifact_path("dst20_v1", "abi.json"), @@ -178,28 +161,18 @@ def setup_variables(self): encoding="utf8", ).read() - # Get registry contract - registry = self.nodes[0].w3.eth.contract( - address=self.nodes[0].w3.to_checksum_address( - "0xdf00000000000000000000000000000000000000" - ), - abi=registry_abi, - ) - - # Get DFI Intrinsics V2 address - self.v2_address = registry.functions.get(1).call() - - self.intrinsics_contract = self.nodes[0].w3.eth.contract( - address=self.v2_address, - abi=self.intinsics_abi, - ) + self.dst20_v2_abi = open( + get_solc_artifact_path("dst20_v2", "abi.json"), + "r", + encoding="utf8", + ).read() # Check META variables - meta_contract = self.nodes[0].w3.eth.contract( - address=self.contract_address_metav1, abi=self.dst20_abi + self.meta_contract = self.nodes[0].w3.eth.contract( + address=self.contract_address_metav1, abi=self.dst20_v2_abi ) - assert_equal(meta_contract.functions.name().call(), "Meta") - assert_equal(meta_contract.functions.symbol().call(), "META") + assert_equal(self.meta_contract.functions.name().call(), "Meta") + assert_equal(self.meta_contract.functions.symbol().call(), "META") # Activate fractional split self.nodes[0].setgov( @@ -280,26 +253,6 @@ def setup_govvars(self): ) self.nodes[0].generate(1) - def check_backwards_compatibility(self): - - # Check version variable - assert_equal(self.intrinsics_contract.functions.version().call(), 2) - - for _ in range(5): - self.nodes[0].generate(1) - - # check evmBlockCount variable - assert_equal( - self.intrinsics_contract.functions.evmBlockCount().call(), - self.nodes[0].w3.eth.get_block_number(), - ) - - # check dvmBlockCount variable - assert_equal( - self.intrinsics_contract.functions.dvmBlockCount().call(), - self.nodes[0].getblockcount(), - ) - def fund_address(self, source, destination, amount=20): # Fund address with META @@ -335,9 +288,7 @@ def fund_address(self, source, destination, amount=20): self.nodes[0].generate(1) # Check META balance - meta_contract = self.nodes[0].w3.eth.contract( - address=self.contract_address_metav1, abi=self.dst20_abi - ) + meta_contract = self.meta_contract assert_equal( meta_contract.functions.balanceOf(destination).call() / math.pow(10, meta_contract.functions.decimals().call()), @@ -582,6 +533,10 @@ def intrinsic_token_split(self, amount, split_multiplier): + (amount * decimal_multiplier), ) + self.execute_split_transaction_at_highest_level( + self.contract_address_metav2, amount + ) + def intrinsic_token_merge(self, amount, split_multiplier): # Rollback @@ -764,12 +719,15 @@ def execute_split_transaction( # Get old contract meta_contract = self.nodes[0].w3.eth.contract( - address=source_contract, abi=self.dst20_abi + address=source_contract, abi=self.dst20_v2_abi ) - # Call migrateTokens - deposit_txn = self.intrinsics_contract.functions.migrateTokens( - source_contract, amount_to_send + totalSupplyBefore = meta_contract.functions.totalSupply().call() + balance_before = meta_contract.functions.balanceOf(self.evm_address).call() + + # Call upgradeToken + deposit_txn = meta_contract.functions.upgradeToken( + amount_to_send ).build_transaction( { "from": self.evm_address, @@ -781,14 +739,19 @@ def execute_split_transaction( signed_txn = self.nodes[0].w3.eth.account.sign_transaction( deposit_txn, self.evm_privkey ) - - balance_before = meta_contract.functions.balanceOf(self.evm_address).call() - total_supply_before = meta_contract.functions.totalSupply().call() - - # Send the signed transaction 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) + + events = meta_contract.events.UpgradeResult().process_log( + list(tx_receipt["logs"])[0] + ) + + assert_equal(events["event"], "UpgradeResult") + assert_equal(events["args"]["newTokenContractAddress"], destination_contract) + assert_equal(events["args"]["newAmount"], amount_to_receive) + # Check source contract balance on sender # Source contract balance of sender should be reduced by the approved amount assert_equal( @@ -799,12 +762,20 @@ def execute_split_transaction( # Check source contract totalSupply. # Funds should be reduced by the amount splitted totalSupplyAfter = meta_contract.functions.totalSupply().call() - assert_equal(totalSupplyAfter, total_supply_before - amount_to_send) + 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_abi + address=destination_contract, abi=self.dst20_v2_abi + ) + + # Check transfer token logs on new contract + events = meta_contract_new.events.Transfer().process_log( + list(tx_receipt["logs"])[1] ) + assert_equal(events["event"], "Transfer") + assert_equal(events["args"]["to"], self.evm_address) + assert_equal(events["args"]["value"], amount_to_receive) # Check META balance on sender assert_equal( @@ -812,13 +783,41 @@ def execute_split_transaction( amount_to_receive, ) - # Check META balance on sender - assert_equal( - meta_contract_new.functions.balanceOf(self.v2_address).call(), - Decimal(0), + 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 ) - return amount_to_receive + amount_to_send = Web3.to_wei(amount, "ether") + + # Check that new contract split does not work + deposit_txn = meta_contract.functions.upgradeToken( + amount_to_send + ).build_transaction( + { + "from": self.evm_address, + "nonce": self.nodes[0].eth_getTransactionCount(self.evm_address), + } + ) + + # Sign the transaction + signed_txn = self.nodes[0].w3.eth.account.sign_transaction( + deposit_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) + + events = meta_contract.events.UpgradeResult().process_log( + list(tx_receipt["logs"])[0] + ) + + assert_equal(events["event"], "UpgradeResult") + assert_equal(events["args"]["newTokenContractAddress"], source_contract) + assert_equal(events["args"]["newAmount"], amount_to_send) if __name__ == "__main__":