feat(CommunityTokens): New deployment contracts and handling signer pub key
New contracts and contract go functions. Adjust owner&master tokens deployment flow. Create deployment signature. CommunityTokens API for handling signer pubkey. Issue #11954
This commit is contained in:
parent
e1354016a0
commit
c85a110a31
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,70 @@
|
|||
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/utils/Context.sol";
|
||||
|
||||
contract CommunityERC20 is Context, Ownable, ERC20 {
|
||||
error CommunityERC20_MaxSupplyLowerThanTotalSupply();
|
||||
error CommunityERC20_MaxSupplyReached();
|
||||
error CommunityERC20_MismatchingAddressesAndAmountsLengths();
|
||||
|
||||
/**
|
||||
* If we want unlimited total supply we should set maxSupply to 2^256-1.
|
||||
*/
|
||||
uint256 public maxSupply;
|
||||
|
||||
uint8 private immutable customDecimals;
|
||||
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
uint8 _decimals,
|
||||
uint256 _maxSupply
|
||||
)
|
||||
ERC20(_name, _symbol)
|
||||
{
|
||||
maxSupply = _maxSupply;
|
||||
customDecimals = _decimals;
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
// External functions
|
||||
|
||||
function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
|
||||
if (newMaxSupply < totalSupply()) {
|
||||
revert CommunityERC20_MaxSupplyLowerThanTotalSupply();
|
||||
}
|
||||
maxSupply = newMaxSupply;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Mint tokens for each address in `addresses` each one with
|
||||
* an amount specified in `amounts`.
|
||||
*
|
||||
*/
|
||||
function mintTo(address[] memory addresses, uint256[] memory amounts) external onlyOwner {
|
||||
if (addresses.length != amounts.length) {
|
||||
revert CommunityERC20_MismatchingAddressesAndAmountsLengths();
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < addresses.length; i++) {
|
||||
uint256 amount = amounts[i];
|
||||
if (totalSupply() + amount > maxSupply) {
|
||||
revert CommunityERC20_MaxSupplyReached();
|
||||
}
|
||||
_mint(addresses[i], amount);
|
||||
}
|
||||
}
|
||||
|
||||
// Public functions
|
||||
function decimals() public view virtual override returns (uint8) {
|
||||
return customDecimals;
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
// Private functions
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
import "./BaseToken.sol";
|
||||
|
||||
contract CollectibleV1 is BaseToken {
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
uint256 _maxSupply,
|
||||
bool _remoteBurnable,
|
||||
bool _transferable,
|
||||
string memory _baseTokenURI,
|
||||
address _ownerToken,
|
||||
address _masterToken
|
||||
)
|
||||
BaseToken(_name, _symbol, _maxSupply, _remoteBurnable, _transferable, _baseTokenURI, _ownerToken, _masterToken)
|
||||
{ }
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,233 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
||||
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||||
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
||||
import { ITokenFactory } from "./interfaces/ITokenFactory.sol";
|
||||
import { IAddressRegistry } from "./interfaces/IAddressRegistry.sol";
|
||||
|
||||
/**
|
||||
* @title CommunityTokenDeployer contract
|
||||
* @author 0x-r4bbit
|
||||
*
|
||||
* This contract serves as a deployment process for Status community owners
|
||||
* to deploy access control token contracts on behalf of their Status community.
|
||||
* The contract keep a reference to token factories that are used for deploying tokens.
|
||||
* The contract deploys the two token contracts `OwnerToken` and `MasterToken` those factories.
|
||||
* The contract maintains a registry which keeps track of `OwnerToken` contract
|
||||
* addresses per community.
|
||||
*
|
||||
* Only one deployment per community can be done.
|
||||
* Status community owners have to provide an EIP712 hash signature that was
|
||||
* created using their community's private key to successfully execute a deployment.
|
||||
*
|
||||
* @notice This contract is used by Status community owners to deploy
|
||||
* community access control token contracts.
|
||||
* @notice This contract maintains a registry that tracks contract addresses
|
||||
* and community addresses
|
||||
* @dev This contract will be deployed by Status, making Status the owner
|
||||
* of the contract.
|
||||
* @dev A contract address for a `CommunityTokenRegistry` contract has to be provided
|
||||
* to create this contract.
|
||||
* @dev A contract address for a `CommunityOwnerTokenFactory` contract has to be provided
|
||||
* to create this contract.
|
||||
* @dev A contract address for a `CommunityMasterTokenFactory` contract has to be provided
|
||||
* to create this contract.
|
||||
* @dev The `CommunityTokenRegistry` address can be changed by the owner of this contract.
|
||||
* @dev The `CommunityOwnerTokenFactory` address can be changed by the owner of this contract.
|
||||
* @dev The `CommunityMasterTokenFactory` address can be changed by the owner of this contract.
|
||||
*/
|
||||
contract CommunityTokenDeployer is EIP712("CommunityTokenDeployer", "1"), Ownable2Step {
|
||||
using ECDSA for bytes32;
|
||||
|
||||
error CommunityTokenDeployer_InvalidDeploymentRegistryAddress();
|
||||
error CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
||||
error CommunityTokenDeployer_EqualFactoryAddresses();
|
||||
error CommunityTokenDeployer_AlreadyDeployed();
|
||||
error CommunityTokenDeployer_InvalidSignerKeyOrCommunityAddress();
|
||||
error CommunityTokenDeployer_InvalidTokenMetadata();
|
||||
error CommunityTokenDeployer_InvalidDeployerAddress();
|
||||
error CommunityTokenDeployer_InvalidDeploymentSignature();
|
||||
|
||||
event OwnerTokenFactoryAddressChange(address indexed);
|
||||
event MasterTokenFactoryAddressChange(address indexed);
|
||||
event DeploymentRegistryAddressChange(address indexed);
|
||||
event DeployOwnerToken(address indexed);
|
||||
event DeployMasterToken(address indexed);
|
||||
|
||||
/// @dev Needed to avoid "Stack too deep" error.
|
||||
struct TokenConfig {
|
||||
string name;
|
||||
string symbol;
|
||||
string baseURI;
|
||||
}
|
||||
|
||||
/// @dev Used to verify signatures.
|
||||
struct DeploymentSignature {
|
||||
address signer;
|
||||
address deployer;
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
}
|
||||
|
||||
bytes32 public constant DEPLOYMENT_SIGNATURE_TYPEHASH = keccak256("Deploy(address signer,address deployer)");
|
||||
|
||||
/// @dev Address of the `CommunityTokenRegistry` contract instance.
|
||||
address public deploymentRegistry;
|
||||
|
||||
/// @dev Address of the `CommunityOwnerTokenFactory` contract instance.
|
||||
address public ownerTokenFactory;
|
||||
|
||||
/// @dev Address of the `CommunityMasterTokenFactory` contract instance.
|
||||
address public masterTokenFactory;
|
||||
|
||||
/// @param _registry The address of the `CommunityTokenRegistry` contract.
|
||||
/// @param _ownerTokenFactory The address of the `CommunityOwnerTokenFactory` contract.
|
||||
/// @param _masterTokenFactory The address of the `CommunityMasterTokenFactory` contract.
|
||||
constructor(address _registry, address _ownerTokenFactory, address _masterTokenFactory) {
|
||||
if (_registry == address(0)) {
|
||||
revert CommunityTokenDeployer_InvalidDeploymentRegistryAddress();
|
||||
}
|
||||
if (_ownerTokenFactory == address(0) || _masterTokenFactory == address(0)) {
|
||||
revert CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
||||
}
|
||||
if (_ownerTokenFactory == _masterTokenFactory) {
|
||||
revert CommunityTokenDeployer_EqualFactoryAddresses();
|
||||
}
|
||||
deploymentRegistry = _registry;
|
||||
ownerTokenFactory = _ownerTokenFactory;
|
||||
masterTokenFactory = _masterTokenFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deploys an instance of `OwnerToken` and `MasterToken` on behalf
|
||||
* of a Status community account, provided `_signature` is valid and was signed
|
||||
* by that Status community account, using the configured factory contracts.
|
||||
* @dev Anyone can call this function but a valid EIP712 hash signature has to be
|
||||
* provided for a successful deployment.
|
||||
* @dev Emits {CreateToken} events via underlying token factories.
|
||||
* @dev Emits {DeployOwnerToken} event.
|
||||
* @dev Emits {DeployMasterToken} event.
|
||||
*
|
||||
* @param _ownerToken A `TokenConfig` containing ERC721 metadata for `OwnerToken`
|
||||
* @param _masterToken A `TokenConfig` containing ERC721 metadata for `MasterToken`
|
||||
* @param _signature A `DeploymentSignature` containing a signer and deployer address,
|
||||
* and a signature created by a Status community
|
||||
* @return address The address of the deployed `OwnerToken` contract.
|
||||
* @return address The address of the deployed `MasterToken` contract.
|
||||
*/
|
||||
function deploy(
|
||||
TokenConfig calldata _ownerToken,
|
||||
TokenConfig calldata _masterToken,
|
||||
DeploymentSignature calldata _signature,
|
||||
bytes memory _signerPublicKey
|
||||
)
|
||||
external
|
||||
returns (address, address)
|
||||
{
|
||||
if (_signature.signer == address(0) || _signerPublicKey.length == 0) {
|
||||
revert CommunityTokenDeployer_InvalidSignerKeyOrCommunityAddress();
|
||||
}
|
||||
|
||||
if (_signature.deployer != msg.sender) {
|
||||
revert CommunityTokenDeployer_InvalidDeployerAddress();
|
||||
}
|
||||
|
||||
if (IAddressRegistry(deploymentRegistry).getEntry(_signature.signer) != address(0)) {
|
||||
revert CommunityTokenDeployer_AlreadyDeployed();
|
||||
}
|
||||
|
||||
if (!_verifySignature(_signature)) {
|
||||
revert CommunityTokenDeployer_InvalidDeploymentSignature();
|
||||
}
|
||||
|
||||
address ownerToken = ITokenFactory(ownerTokenFactory).create(
|
||||
_ownerToken.name, _ownerToken.symbol, _ownerToken.baseURI, msg.sender, _signerPublicKey
|
||||
);
|
||||
|
||||
emit DeployOwnerToken(ownerToken);
|
||||
|
||||
address masterToken = ITokenFactory(masterTokenFactory).create(
|
||||
_masterToken.name, _masterToken.symbol, _masterToken.baseURI, ownerToken, bytes("")
|
||||
);
|
||||
|
||||
emit DeployMasterToken(masterToken);
|
||||
|
||||
IAddressRegistry(deploymentRegistry).addEntry(_signature.signer, ownerToken);
|
||||
return (ownerToken, masterToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets a deployment registry address.
|
||||
* @dev Only the owner can call this function.
|
||||
* @dev Emits a {DeploymentRegistryAddressChange} event.
|
||||
* @dev Reverts if the provided address is a zero address.
|
||||
*
|
||||
* @param _deploymentRegistry The address of the deployment registry contract.
|
||||
*/
|
||||
function setDeploymentRegistryAddress(address _deploymentRegistry) external onlyOwner {
|
||||
if (_deploymentRegistry == address(0)) {
|
||||
revert CommunityTokenDeployer_InvalidDeploymentRegistryAddress();
|
||||
}
|
||||
deploymentRegistry = _deploymentRegistry;
|
||||
emit DeploymentRegistryAddressChange(deploymentRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the `OwnerToken` factory contract address.
|
||||
* @dev Only the owner can call this function.
|
||||
* @dev Emits a {OwnerTokenFactoryChange} event.
|
||||
* @dev Reverts if the provided address is a zero address.
|
||||
*
|
||||
* @param _ownerTokenFactory The address of the `OwnerToken` factory contract.
|
||||
*/
|
||||
function setOwnerTokenFactoryAddress(address _ownerTokenFactory) external onlyOwner {
|
||||
if (_ownerTokenFactory == address(0)) {
|
||||
revert CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
||||
}
|
||||
ownerTokenFactory = _ownerTokenFactory;
|
||||
emit OwnerTokenFactoryAddressChange(ownerTokenFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the `MasterToken` factory contract address.
|
||||
* @dev Only the owner can call this function.
|
||||
* @dev Emits a {MasterTokenFactoryChange} event.
|
||||
* @dev Reverts if the provided address is a zero address.
|
||||
*
|
||||
* @param _masterTokenFactory The address of the `MasterToken` factory contract.
|
||||
*/
|
||||
function setMasterTokenFactoryAddress(address _masterTokenFactory) external onlyOwner {
|
||||
if (_masterTokenFactory == address(0)) {
|
||||
revert CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
||||
}
|
||||
masterTokenFactory = _masterTokenFactory;
|
||||
emit MasterTokenFactoryAddressChange(masterTokenFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns an EIP712 domain separator hash
|
||||
* @return bytes32 An EIP712 domain separator hash
|
||||
*/
|
||||
function DOMAIN_SEPARATOR() external view returns (bytes32) {
|
||||
return _domainSeparatorV4();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verifies provided `DeploymentSignature` which was created by
|
||||
* the Status community account for which the access control token contracts
|
||||
* will be deployed.
|
||||
* @dev This contract does not maintain nonces for the typed data hash, which
|
||||
* is typically done to prevent signature replay attacks. The `deploy()` function
|
||||
* allows only one deployment per Status community, so replay attacks are not possible.
|
||||
* @return bool Whether the provided signature could be recovered.
|
||||
*/
|
||||
function _verifySignature(DeploymentSignature calldata signature) internal view returns (bool) {
|
||||
bytes32 digest =
|
||||
_hashTypedDataV4(keccak256(abi.encode(DEPLOYMENT_SIGNATURE_TYPEHASH, signature.signer, signature.deployer)));
|
||||
return signature.signer == digest.recover(signature.v, signature.r, signature.s);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package communitytokendeployer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var errorNotAvailableOnChainID = errors.New("deployer contract not available for chainID")
|
||||
|
||||
// TODO add addresses for other chains
|
||||
var contractAddressByChainID = map[uint64]common.Address{
|
||||
420: common.HexToAddress("0xfFa8A255D905c909379859eA45B959D090DDC2d4"), // Optimism Goerli
|
||||
}
|
||||
|
||||
func ContractAddress(chainID uint64) (common.Address, error) {
|
||||
addr, exists := contractAddressByChainID[chainID]
|
||||
if !exists {
|
||||
return *new(common.Address), errorNotAvailableOnChainID
|
||||
}
|
||||
return addr, nil
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
import "./BaseToken.sol";
|
||||
|
||||
contract MasterToken is BaseToken {
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
string memory _baseTokenURI,
|
||||
address _ownerToken
|
||||
)
|
||||
BaseToken(_name, _symbol, type(uint256).max, true, false, _baseTokenURI, _ownerToken, address(0x0))
|
||||
{ }
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,31 @@
|
|||
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
import "./BaseToken.sol";
|
||||
|
||||
contract OwnerToken is BaseToken {
|
||||
bytes public signerPublicKey;
|
||||
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
string memory _baseTokenURI,
|
||||
address _receiver,
|
||||
bytes memory _signerPublicKey
|
||||
)
|
||||
BaseToken(_name, _symbol, 1, false, true, _baseTokenURI, address(this), address(this))
|
||||
{
|
||||
signerPublicKey = _signerPublicKey;
|
||||
address[] memory addresses = new address[](1);
|
||||
addresses[0] = _receiver;
|
||||
_mintTo(addresses);
|
||||
}
|
||||
|
||||
function setMaxSupply(uint256 _newMaxSupply) external override onlyOwner {
|
||||
revert("max supply locked");
|
||||
}
|
||||
|
||||
function setSignerPublicKey(bytes memory _newSignerPublicKey) external onlyOwner {
|
||||
signerPublicKey = _newSignerPublicKey;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,91 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
||||
import { IAddressRegistry } from "./interfaces/IAddressRegistry.sol";
|
||||
import { OwnerToken } from "./tokens/OwnerToken.sol";
|
||||
|
||||
/**
|
||||
* @title CommunityOwnerTokenRegistry contract
|
||||
* @author 0x-r4bbit
|
||||
*
|
||||
* This contract serves as a simple registry to map Status community addresses
|
||||
* to Status community `OwnerToken` addresses.
|
||||
* The `CommunityTokenDeployer` contract uses this registry contract to maintain
|
||||
* a list of community address and their token addresses.
|
||||
* @notice This contract will be deployed by Status similar to the `CommunityTokenDeployer`
|
||||
* contract.
|
||||
* @notice This contract maps community addresses to `OwnerToken` addresses.
|
||||
* @notice Only one entry per community address can exist in the registry.
|
||||
* @dev This registry has been extracted into its own contract so that it's possible
|
||||
* to introduce different version of a `CommunityDeployerContract` without needing to
|
||||
* migrate existing registry data, as the deployer contract would simply point at this
|
||||
* registry contract.
|
||||
* @dev Only `tokenDeployer` can add entries to the registry.
|
||||
*/
|
||||
contract CommunityOwnerTokenRegistry is IAddressRegistry, Ownable2Step {
|
||||
error CommunityOwnerTokenRegistry_NotAuthorized();
|
||||
error CommunityOwnerTokenRegistry_EntryAlreadyExists();
|
||||
error CommunityOwnerTokenRegistry_InvalidAddress();
|
||||
|
||||
event TokenDeployerAddressChange(address indexed);
|
||||
event AddEntry(address indexed, address indexed);
|
||||
|
||||
/// @dev The address of the token deployer contract.
|
||||
address public tokenDeployer;
|
||||
|
||||
mapping(address => address) public communityAddressToTokenAddress;
|
||||
|
||||
modifier onlyTokenDeployer() {
|
||||
if (msg.sender != tokenDeployer) {
|
||||
revert CommunityOwnerTokenRegistry_NotAuthorized();
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the address of the community token deployer contract. This is needed to
|
||||
* ensure only the known token deployer contract can add new entries to the registry.
|
||||
* @dev Only the owner of this contract can call this function.
|
||||
* @dev Emits a {TokenDeployerAddressChange} event.
|
||||
*
|
||||
* @param _tokenDeployer The address of the community token deployer contract
|
||||
*/
|
||||
function setCommunityTokenDeployerAddress(address _tokenDeployer) external onlyOwner {
|
||||
if (_tokenDeployer == address(0)) {
|
||||
revert CommunityOwnerTokenRegistry_InvalidAddress();
|
||||
}
|
||||
tokenDeployer = _tokenDeployer;
|
||||
emit TokenDeployerAddressChange(tokenDeployer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds an entry to the registry. Only one entry per community address can exist.
|
||||
* @dev Only the token deployer contract can call this function.
|
||||
* @dev Reverts when the entry already exists.
|
||||
* @dev Reverts when either `_communityAddress` or `_tokenAddress` are zero addresses.
|
||||
* @dev Emits a {AddEntry} event.
|
||||
*/
|
||||
function addEntry(address _communityAddress, address _tokenAddress) external onlyTokenDeployer {
|
||||
if (getEntry(_communityAddress) != address(0)) {
|
||||
revert CommunityOwnerTokenRegistry_EntryAlreadyExists();
|
||||
}
|
||||
|
||||
if (_communityAddress == address(0) || _tokenAddress == address(0)) {
|
||||
revert CommunityOwnerTokenRegistry_InvalidAddress();
|
||||
}
|
||||
|
||||
communityAddressToTokenAddress[_communityAddress] = _tokenAddress;
|
||||
emit AddEntry(_communityAddress, _tokenAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the owner token address for a given community address.
|
||||
* @param _communityAddress The community address to look up an owner token address.
|
||||
* @return address The owner token address for the community addres, or zero address .
|
||||
*/
|
||||
function getEntry(address _communityAddress) public view returns (address) {
|
||||
return communityAddressToTokenAddress[_communityAddress];
|
||||
}
|
||||
}
|
|
@ -4933,3 +4933,21 @@ func (m *Manager) GetCommunityRequestsToJoinWithRevealedAddresses(communityID ty
|
|||
func (m *Manager) SaveCommunity(community *Community) error {
|
||||
return m.persistence.SaveCommunity(community)
|
||||
}
|
||||
|
||||
func (m *Manager) CreateCommunityTokenDeploymentSignature(ctx context.Context, chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
community, err := m.GetByIDString(communityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if community == nil {
|
||||
return nil, ErrOrgNotFound
|
||||
}
|
||||
if !community.IsControlNode() {
|
||||
return nil, ErrNotControlNode
|
||||
}
|
||||
digest, err := m.communityTokensService.DeploymentSignatureDigest(chainID, addressFrom, communityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypto.Sign(digest, community.PrivateKey())
|
||||
}
|
||||
|
|
|
@ -118,6 +118,10 @@ func (c *CollectiblesServiceMock) SetMockAssetContractData(chainID uint64, contr
|
|||
c.Assets[chainID][contractAddress] = assetData
|
||||
}
|
||||
|
||||
func (c *CollectiblesServiceMock) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
return gethcommon.Hex2Bytes("ccbb375343347491706cf4b43796f7b96ccc89c9e191a8b78679daeba1684ec7"), nil
|
||||
}
|
||||
|
||||
func newMessenger(s *suite.Suite, shh types.Waku, logger *zap.Logger, password string, walletAddresses []string,
|
||||
mockedBalances *map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big, collectiblesService communitytokens.ServiceInterface) *Messenger {
|
||||
accountsManagerMock := &AccountManagerMock{}
|
||||
|
|
|
@ -5077,6 +5077,10 @@ func (m *Messenger) SignMessage(message string) ([]byte, error) {
|
|||
return crypto.Sign(hash, m.identity)
|
||||
}
|
||||
|
||||
func (m *Messenger) CreateCommunityTokenDeploymentSignature(ctx context.Context, chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
return m.communitiesManager.CreateCommunityTokenDeploymentSignature(ctx, chainID, addressFrom, communityID)
|
||||
}
|
||||
|
||||
func (m *Messenger) getTimesource() common.TimeSource {
|
||||
return m.transport
|
||||
}
|
||||
|
|
|
@ -14,8 +14,10 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||
communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/mastertoken"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
|
||||
communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/services/utils"
|
||||
|
@ -129,12 +131,48 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme
|
|||
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
|
||||
}
|
||||
|
||||
func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
|
||||
func decodeSignature(sig []byte) (r [32]byte, s [32]byte, v uint8, err error) {
|
||||
if len(sig) != crypto.SignatureLength {
|
||||
return [32]byte{}, [32]byte{}, 0, fmt.Errorf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)
|
||||
}
|
||||
copy(r[:], sig[:32])
|
||||
copy(s[:], sig[32:64])
|
||||
v = sig[64] + 27
|
||||
return r, s, v, nil
|
||||
}
|
||||
|
||||
func prepareDeploymentSignatureStruct(signature string, communityID string, addressFrom common.Address) (communitytokendeployer.CommunityTokenDeployerDeploymentSignature, error) {
|
||||
r, s, v, err := decodeSignature(common.FromHex(signature))
|
||||
if err != nil {
|
||||
return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err
|
||||
}
|
||||
communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID)
|
||||
if err != nil {
|
||||
return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err
|
||||
}
|
||||
communitySignature := communitytokendeployer.CommunityTokenDeployerDeploymentSignature{
|
||||
V: v,
|
||||
R: r,
|
||||
S: s,
|
||||
Deployer: addressFrom,
|
||||
Signer: communityEthAddress,
|
||||
}
|
||||
return communitySignature, nil
|
||||
}
|
||||
|
||||
func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64,
|
||||
ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
|
||||
signature string, communityID string, signerPubKey string,
|
||||
txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
|
||||
err := ownerTokenParameters.Validate(false)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
if len(signerPubKey) <= 0 {
|
||||
return DeploymentDetails{}, fmt.Errorf("signerPubKey is empty")
|
||||
}
|
||||
|
||||
err = masterTokenParameters.Validate(false)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
|
@ -142,18 +180,32 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, ownerToken
|
|||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
signerPubKey := []byte{}
|
||||
ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: ownerTokenParameters.Name,
|
||||
Symbol: ownerTokenParameters.Symbol,
|
||||
BaseURI: ownerTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: masterTokenParameters.Name,
|
||||
Symbol: masterTokenParameters.Symbol,
|
||||
BaseURI: masterTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
communitySignature, err := prepareDeploymentSignatureStruct(signature, communityID, common.Address(txArgs.From))
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
log.Debug("Signature:", communitySignature)
|
||||
|
||||
tx, err := deployerContractInst.Deploy(transactOpts, ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey))
|
||||
|
||||
address, tx, _, err := ownertoken.DeployOwnerToken(transactOpts, ethClient, ownerTokenParameters.Name,
|
||||
ownerTokenParameters.Symbol, ownerTokenParameters.TokenURI,
|
||||
masterTokenParameters.Name, masterTokenParameters.Symbol,
|
||||
masterTokenParameters.TokenURI, signerPubKey)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
|
@ -171,7 +223,7 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, ownerToken
|
|||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
|
||||
return DeploymentDetails{"", tx.Hash().Hex()}, nil
|
||||
}
|
||||
|
||||
func (api *API) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
|
||||
|
@ -185,26 +237,57 @@ func (api *API) GetMasterTokenContractAddressFromHash(ctx context.Context, chain
|
|||
return "", err
|
||||
}
|
||||
|
||||
logMasterTokenCreatedSig := []byte("MasterTokenCreated(address)")
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
logMasterTokenCreatedSig := []byte("DeployMasterToken(address)")
|
||||
logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig)
|
||||
|
||||
for _, vLog := range receipt.Logs {
|
||||
if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() {
|
||||
ownerTokenABI, err := abi.JSON(strings.NewReader(ownertoken.OwnerTokenABI))
|
||||
event, err := deployerContractInst.ParseDeployMasterToken(*vLog)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
event := new(ownertoken.OwnerTokenMasterTokenCreated)
|
||||
err = ownerTokenABI.UnpackIntoInterface(event, "MasterTokenCreated", vLog.Data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return event.MasterToken.Hex(), nil
|
||||
return event.Arg0.Hex(), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("can't find master token address in transaction: %v", txHash)
|
||||
}
|
||||
|
||||
func (api *API) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
logOwnerTokenCreatedSig := []byte("DeployOwnerToken(address)")
|
||||
logOwnerTokenCreatedSigHash := crypto.Keccak256Hash(logOwnerTokenCreatedSig)
|
||||
|
||||
for _, vLog := range receipt.Logs {
|
||||
if vLog.Topics[0].Hex() == logOwnerTokenCreatedSigHash.Hex() {
|
||||
event, err := deployerContractInst.ParseDeployOwnerToken(*vLog)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return event.Arg0.Hex(), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("can't find owner token address in transaction: %v", txHash)
|
||||
}
|
||||
|
||||
func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
|
||||
|
||||
err := deploymentParameters.Validate(true)
|
||||
|
@ -220,8 +303,9 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara
|
|||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
const decimals = 18
|
||||
address, tx, _, err := assets.DeployAssets(transactOpts, ethClient, deploymentParameters.Name,
|
||||
deploymentParameters.Symbol, deploymentParameters.GetSupply())
|
||||
deploymentParameters.Symbol, decimals, deploymentParameters.GetSupply())
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
|
@ -244,20 +328,105 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara
|
|||
|
||||
// Returns gas units + 10%
|
||||
func (api *API) DeployCollectiblesEstimate(ctx context.Context) (uint64, error) {
|
||||
gasAmount := uint64(2091605)
|
||||
// TODO investigate why the code below does not return correct values
|
||||
/*ethClient, err := api.s.manager.rpcClient.EthClient(420)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, err := collectiblesABI.Pack("", "name", "SYMBOL", big.NewInt(20), true, false, "tokenUriwhcih is very long asdkfjlsdkjflk",
|
||||
common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"), common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
callMsg := ethereum.CallMsg{
|
||||
From: common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"),
|
||||
To: nil,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + uint64(float32(estimate)*0.1), nil*/
|
||||
|
||||
// TODO compute fee dynamically
|
||||
// the code above returns too low fees, need to investigate
|
||||
gasAmount := uint64(2500000)
|
||||
return gasAmount + uint64(float32(gasAmount)*0.1), nil
|
||||
}
|
||||
|
||||
// Returns gas units + 10%
|
||||
func (api *API) DeployAssetsEstimate(ctx context.Context) (uint64, error) {
|
||||
gasAmount := uint64(957483)
|
||||
// TODO compute fee dynamically
|
||||
gasAmount := uint64(1500000)
|
||||
return gasAmount + uint64(float32(gasAmount)*0.1), nil
|
||||
}
|
||||
|
||||
// Returns gas units + 10%
|
||||
func (api *API) DeployOwnerTokenEstimate(ctx context.Context) (uint64, error) {
|
||||
ownerGasAmount := uint64(4389457)
|
||||
return ownerGasAmount + uint64(float32(ownerGasAmount)*0.1), nil
|
||||
func (api *API) DeployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string,
|
||||
ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
|
||||
signature string, communityID string, signerPubKey string) (uint64, error) {
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
deployerAddress, err := communitytokendeployer.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
deployerABI, err := abi.JSON(strings.NewReader(communitytokendeployer.CommunityTokenDeployerABI))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: ownerTokenParameters.Name,
|
||||
Symbol: ownerTokenParameters.Symbol,
|
||||
BaseURI: ownerTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: masterTokenParameters.Name,
|
||||
Symbol: masterTokenParameters.Symbol,
|
||||
BaseURI: masterTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
communitySignature, err := prepareDeploymentSignatureStruct(signature, communityID, common.HexToAddress(fromAddress))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, err := deployerABI.Pack("deploy", ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey))
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
toAddr := deployerAddress
|
||||
fromAddr := common.HexToAddress(fromAddress)
|
||||
|
||||
callMsg := ethereum.CallMsg{
|
||||
From: fromAddr,
|
||||
To: &toAddr,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + uint64(float32(estimate)*0.1), nil
|
||||
}
|
||||
|
||||
func (api *API) NewMasterTokenInstance(chainID uint64, contractAddress string) (*mastertoken.MasterToken, error) {
|
||||
|
@ -268,6 +437,26 @@ func (api *API) NewMasterTokenInstance(chainID uint64, contractAddress string) (
|
|||
return mastertoken.NewMasterToken(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (api *API) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) {
|
||||
backend, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ownertoken.NewOwnerToken(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (api *API) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) {
|
||||
return api.s.manager.NewCommunityTokenDeployerInstance(chainID)
|
||||
}
|
||||
|
||||
func (api *API) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) {
|
||||
backend, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return communityownertokenregistry.NewCommunityOwnerTokenRegistry(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (api *API) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
||||
return api.s.manager.NewCollectiblesInstance(chainID, contractAddress)
|
||||
}
|
||||
|
@ -679,3 +868,91 @@ func (api *API) estimateMethod(ctx context.Context, chainID uint64, contractAddr
|
|||
}
|
||||
return estimate + uint64(float32(estimate)*0.1), nil
|
||||
}
|
||||
|
||||
// Gets signer public key from smart contract with a given chainId and address
|
||||
func (api *API) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signerPubKey, err := contractInst.SignerPublicKey(callOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return common.Bytes2Hex(signerPubKey), nil
|
||||
}
|
||||
|
||||
// Gets signer public key directly from deployer contract
|
||||
func (api *API) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) {
|
||||
// 1. Get Owner Token contract address from deployer contract - SafeGetOwnerTokenAddress()
|
||||
ownerTokenAddr, err := api.SafeGetOwnerTokenAddress(ctx, chainID, communityID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 2. Get Signer from owner token contract - GetSignerPubKey()
|
||||
return api.GetSignerPubKey(ctx, chainID, ownerTokenAddr)
|
||||
}
|
||||
|
||||
// Gets owner token contract address from deployer contract
|
||||
func (api *API) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
registryAddr, err := deployerContractInst.DeploymentRegistry(callOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
registryContractInst, err := api.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ownerTokenAddress, err := registryContractInst.GetEntry(callOpts, communityEthAddress)
|
||||
|
||||
return ownerTokenAddress.Hex(), err
|
||||
}
|
||||
|
||||
func (api *API) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) {
|
||||
if len(newSignerPubKey) <= 0 {
|
||||
return "", fmt.Errorf("signerPubKey is empty")
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tx, err := contractInst.SetSignerPublicKey(transactOpts, common.FromHex(newSignerPubKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.SetSignerPublicKey,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
func (api *API) EstimateSetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (uint64, error) {
|
||||
if len(newSignerPubKey) <= 0 {
|
||||
return 0, fmt.Errorf("signerPubKey is empty")
|
||||
}
|
||||
return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "setSignerPublicKey", newSignerPubKey)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
|
@ -70,3 +72,25 @@ func TestDeploymentParameters(t *testing.T) {
|
|||
requiredSupply = infiniteSupplyParams.GetInfiniteSupply()
|
||||
require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply)
|
||||
}
|
||||
|
||||
func TestTypedDataHash(t *testing.T) {
|
||||
sigHash := common.Hex2Bytes("dd91c30357aafeb2792b5f0facbd83995943c1ea113a906ebbeb58bfeb27dfc2")
|
||||
domainSep := common.Hex2Bytes("4a672b5a08e88d37f7239165a0c9e03a01196587d52c638c0c99cbee5ba527c8")
|
||||
contractAddr := "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
|
||||
signer := "0x54e3922e97e334905fb489be7c5df1f83cb1ce58"
|
||||
deployer := "0x7c8999dC9a822c1f0Df42023113EDB4FDd543266"
|
||||
goodHashResult := "0xccbb375343347491706cf4b43796f7b96ccc89c9e191a8b78679daeba1684ec7"
|
||||
|
||||
typedHash, err := typedStructuredDataHash(domainSep, signer, deployer, contractAddr, 420)
|
||||
require.NoError(t, err, "creating typed structured data hash")
|
||||
require.Equal(t, goodHashResult, typedHash.String())
|
||||
|
||||
customTypedHash := customTypedStructuredDataHash(domainSep, sigHash, signer, deployer)
|
||||
require.Equal(t, goodHashResult, customTypedHash.String())
|
||||
}
|
||||
|
||||
func TestCompressedKeyToEthAddress(t *testing.T) {
|
||||
ethAddr, err := convert33BytesPubKeyToEthAddress("0x02bcbe39785b55a22383f82ac631ea7500e204627369c4ea01d9296af0ea573f57")
|
||||
require.NoError(t, err, "converting pub key to address")
|
||||
require.Equal(t, "0x0A1ec0002dDB927B03049F1aD8D589aBEA4Ba4b3", ethAddr.Hex())
|
||||
}
|
||||
|
|
|
@ -2,11 +2,17 @@ package communitytokens
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||
communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
@ -29,6 +35,18 @@ func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string
|
|||
return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) {
|
||||
backend, err := m.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend)
|
||||
}
|
||||
|
||||
func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
||||
contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
|
@ -97,3 +115,94 @@ func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (
|
|||
InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) {
|
||||
decoded, err := types.DecodeHex(pubKey)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
communityPubKey, err := crypto.DecompressPubkey(decoded)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil
|
||||
}
|
||||
|
||||
// Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference.
|
||||
func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash {
|
||||
// every field should be 32 bytes, eth address is 20 bytes so padding should be added
|
||||
emptyOffset := [12]byte{}
|
||||
hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(),
|
||||
emptyOffset[:], common.HexToAddress(deployer).Bytes())
|
||||
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes()))
|
||||
return crypto.Keccak256Hash(rawData)
|
||||
}
|
||||
|
||||
// Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712
|
||||
// Domain separator from smart contract is used.
|
||||
func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) {
|
||||
myTypedData := apitypes.TypedData{
|
||||
Types: apitypes.Types{
|
||||
"Deploy": []apitypes.Type{
|
||||
{Name: "signer", Type: "address"},
|
||||
{Name: "deployer", Type: "address"},
|
||||
},
|
||||
"EIP712Domain": []apitypes.Type{
|
||||
{Name: "name", Type: "string"},
|
||||
{Name: "version", Type: "string"},
|
||||
{Name: "chainId", Type: "uint256"},
|
||||
{Name: "verifyingContract", Type: "address"},
|
||||
},
|
||||
},
|
||||
PrimaryType: "Deploy",
|
||||
// Domain field should be here to keep correct structure but
|
||||
// domainSeparator from smart contract is used.
|
||||
Domain: apitypes.TypedDataDomain{
|
||||
Name: "CommunityTokenDeployer", // name from Deployer smart contract
|
||||
Version: "1", // version from Deployer smart contract
|
||||
ChainId: math.NewHexOrDecimal256(int64(chainID)),
|
||||
VerifyingContract: deployerContractAddress,
|
||||
},
|
||||
Message: apitypes.TypedDataMessage{
|
||||
"signer": signer,
|
||||
"deployer": addressFrom,
|
||||
},
|
||||
}
|
||||
|
||||
typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message)
|
||||
if err != nil {
|
||||
return types.Hash{}, err
|
||||
}
|
||||
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash)))
|
||||
return crypto.Keccak256Hash(rawData), nil
|
||||
}
|
||||
|
||||
// Creates
|
||||
func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
callOpts := &bind.CallOpts{Pending: false}
|
||||
communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return structedHash.Bytes(), nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
type ServiceInterface interface {
|
||||
GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error)
|
||||
GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error)
|
||||
DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error)
|
||||
}
|
||||
|
||||
// Collectibles service
|
||||
|
@ -70,3 +71,7 @@ func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress str
|
|||
func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error) {
|
||||
return s.manager.GetAssetContractData(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (s *Service) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
return s.manager.DeploymentSignatureDigest(chainID, addressFrom, communityID)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/mastertoken"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
|
||||
"github.com/status-im/status-go/protocol/communities/token"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
|
@ -24,6 +25,32 @@ type TokenInstance interface {
|
|||
PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
// Owner Token
|
||||
type OwnerTokenInstance struct {
|
||||
TokenInstance
|
||||
instance *ownertoken.OwnerToken
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("remote destruction for owner token not implemented")
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("minting for owner token not implemented")
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("setting max supply for owner token not implemented")
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) {
|
||||
ownerTokenABI, err := abi.JSON(strings.NewReader(ownertoken.OwnerTokenABI))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return ownerTokenABI.Pack(methodName, args...)
|
||||
}
|
||||
|
||||
// Master Token
|
||||
type MasterTokenInstance struct {
|
||||
TokenInstance
|
||||
|
@ -88,7 +115,7 @@ type AssetInstance struct {
|
|||
}
|
||||
|
||||
func (t AssetInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("remote burn not implemented")
|
||||
return nil, fmt.Errorf("remote destruction for assets not implemented")
|
||||
}
|
||||
|
||||
// The amount should be in smallest denomination of the asset (like wei) with decimal = 18, eg.
|
||||
|
@ -122,6 +149,12 @@ func NewTokenInstance(api *API, chainID uint64, contractAddress string) (TokenIn
|
|||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case privLevel == token.OwnerLevel:
|
||||
contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &OwnerTokenInstance{instance: contractInst}, nil
|
||||
case privLevel == token.MasterLevel:
|
||||
contractInst, err := api.NewMasterTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
|
|
|
@ -994,6 +994,10 @@ func (api *PublicAPI) SignMessageWithChatKey(ctx context.Context, message string
|
|||
return api.service.messenger.SignMessage(message)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) CreateCommunityTokenDeploymentSignature(ctx context.Context, chainID uint64, addressFrom string, communityID string) (types.HexBytes, error) {
|
||||
return api.service.messenger.CreateCommunityTokenDeploymentSignature(ctx, chainID, addressFrom, communityID)
|
||||
}
|
||||
|
||||
// wallet connect session apis
|
||||
func (api *PublicAPI) AddWalletConnectSession(ctx context.Context, request *requests.AddWalletConnectSession) error {
|
||||
return api.service.messenger.AddWalletConnectSession(request)
|
||||
|
|
|
@ -358,6 +358,7 @@ const (
|
|||
RemoteDestructCollectible PendingTrxType = "RemoteDestructCollectible"
|
||||
BurnCommunityToken PendingTrxType = "BurnCommunityToken"
|
||||
DeployOwnerToken PendingTrxType = "DeployOwnerToken"
|
||||
SetSignerPublicKey PendingTrxType = "SetSignerPublicKey"
|
||||
)
|
||||
|
||||
type PendingTransaction struct {
|
||||
|
|
Loading…
Reference in New Issue