Skip to content

Commit

Permalink
Merge pull request #3 from Pixura/migration_scripts
Browse files Browse the repository at this point in the history
Migration scripts
  • Loading branch information
charlescrain authored Mar 16, 2020
2 parents 0f6f4a6 + 5d65a52 commit 631c219
Show file tree
Hide file tree
Showing 12 changed files with 3,473 additions and 73 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ node_modules
output/
.psc-ide-port
.purs-repl
**/__compiler/
**/__compiler/
deploy-configs/
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
.PHONY: help clean hlint stylish init compile-contracts purs-contract-gen hs-build purs-build purs-build-all deploy-test-chain takedown-test-chain run-contract-tests contract-tests
.PHONY: help clean hlint stylish init compile-contracts purs-contract-gen hs-build purs-build purs-build-all deploy-test-chain takedown-test-chain run-contract-tests contract-tests migrate-marketplaceV2
.DEFAULT_GOAL := help

######################################################
#### Env
######################################################
MARKETPLACEV2_CONFIG ?= "./deploy-configs/marketplacev2.json"

######################################################
#### Utils
######################################################

help: ## Ask for help!
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

clean: ## clean stack
stack clean
Expand Down Expand Up @@ -63,6 +68,13 @@ purs-build-all: ## Compiles contracts, codegens purescript bindings, and builds
make purs-contract-gen && \
make purs-build

######################################################
#### Migrations
######################################################

migrate-marketplaceV2: ## Deploy test environment and run contract tests
CONFIG=$(MARKETPLACEV2_CONFIG) \
yarn spago run --main Migrations.SuperRareMarketAuctionV2

######################################################
#### Test
Expand Down
19 changes: 17 additions & 2 deletions contracts/v5/build/SuperRareMarketAuctionV2.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/v5/src/SuperRareMarketAuctionV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract SuperRareMarketAuctionV2 is Ownable, SendValueOrEscrow {
uint256 public marketplaceFee = 3; // 3 %

// Royalty fee paid to the creator of a token on secondary sales.
uint256 public royaltyFee = 3; // 3 %
uint256 public royaltyFee = 5; // 5 %

// Primary sale fee split.
uint256 public primarySaleFee = 15; // 15 %
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"openzeppelin-solidity-solc4": "https://github.com/OpenZeppelin/openzeppelin-contracts#v2.0.0",
"rlp": "^2.2.4",
"secp256k1": "^4.0.0",
"zeppelin-solidity": "1.6.0"
"zeppelin-solidity": "1.6.0",
"@truffle/hdwallet-provider": "1.0.33"
}
}
25 changes: 25 additions & 0 deletions packages.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,31 @@ let additions =
, repo = "https://github.com/f-o-a-m/chanterelle.git"
, version = "v4.0.0"
}
, truffle-hd-wallet =
{ dependencies = [ "web3" ]
, repo =
"https://github.com/Pixura/purescript-truffle-hd-wallet-provider/"
, version = "v0.1.0"
}
, simple-gql-query =
{ dependencies =
[ "aff"
, "aff-promise"
, "affjax"
, "console"
, "effect"
, "exceptions"
, "generics-rep"
, "node-fs-aff"
, "parsing"
, "prelude"
, "prettier"
, "psci-support"
, "simple-json"
]
, repo = "https://github.com/charlescrain/purescript-simple-gql-query"
, version = "v1.0.0"
}
}

in upstream overrides additions
32 changes: 11 additions & 21 deletions purs-contracts/src/Deploy/Contracts/SuperRareMarketAuctionV2.purs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
module Deploy.Contracts.SuperRareMarketAuctionV2 where

import Prelude
import Chanterelle.Deploy (deployContract)
import Chanterelle.Internal.Deploy (DeployReceipt)
import Chanterelle.Internal.Types (DeployM, DeployConfig(..), ContractConfig, NoArgs, noArgs, constructorNoArgs)
import Control.Monad.Reader.Class (ask)
import Data.Lens ((?~))
import Data.Maybe (fromJust)
import Network.Ethereum.Core.BigNumber (decimal, parseBigNumber)
import Network.Ethereum.Web3 (_from, _gas, _gasPrice, defaultTransactionOptions)
import Partial.Unsafe (unsafePartial)
import Chanterelle.Internal.Types (ContractConfig, DeployM, NoArgs, constructorNoArgs, noArgs)
import Deploy.Utils (GasSettings, deployContractWithConfig)
import Migrations.Utils (emptyGasSettings)

makeSuperRareMarketAuctionV2Config :: ContractConfig NoArgs
makeSuperRareMarketAuctionV2Config =
Expand All @@ -24,18 +19,13 @@ type DeployResults
)

deployScript :: DeployM (Record DeployResults)
deployScript = do
deployCfg@(DeployConfig { primaryAccount, provider }) <- ask
let
bigGasLimit = unsafePartial fromJust $ parseBigNumber decimal "67123880"
deployScript = deployScriptWithGasSettings emptyGasSettings

bigGasPrice = unsafePartial fromJust $ parseBigNumber decimal "10000000000"

txOpts =
defaultTransactionOptions # _from ?~ primaryAccount
# _gas
?~ bigGasLimit
# _gasPrice
?~ bigGasPrice
superRareMarketAuctionV2 <- deployContract txOpts makeSuperRareMarketAuctionV2Config
deployScriptWithGasSettings :: GasSettings -> DeployM (Record DeployResults)
deployScriptWithGasSettings gasSettings = do
superRareMarketAuctionV2 <-
deployContractWithConfig
{ contractConfig: makeSuperRareMarketAuctionV2Config
, gasSettings
}
pure { superRareMarketAuctionV2 }
77 changes: 74 additions & 3 deletions purs-contracts/src/Deploy/Utils.purs
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
module Deploy.Utils where

import Prelude
import Chanterelle.Deploy (deployContract)
import Chanterelle.Internal.Deploy (DeployReceipt)
import Chanterelle.Internal.Types (ContractConfig, DeployConfig(DeployConfig), DeployM)
import Chanterelle.Internal.Utils (pollTransactionReceipt)
import Control.Monad.Reader (ask)
import Control.Monad.Error.Class (throwError)
import Control.Monad.Reader.Class (ask)
import Data.Lens ((.~), (?~), (^.))
import Data.List.NonEmpty (singleton)
import Data.Maybe (Maybe(..), fromJust, maybe)
import Effect.Aff.Class (class MonadAff)
import Foreign (ForeignError(..))
import Network.Ethereum.Core.BigNumber (decimal, parseBigNumber)
import Network.Ethereum.Core.HexString (HexString)
import Network.Ethereum.Web3 (Provider, TransactionReceipt(..), TransactionStatus(..), Web3)
import Partial.Unsafe (unsafeCrashWith)
import Network.Ethereum.Web3 (BigNumber, Provider, TransactionOptions, TransactionReceipt(..), TransactionStatus(..), Web3, _from, _gas, _gasPrice, defaultTransactionOptions)
import Network.Ethereum.Web3.Types (NoPay)
import Partial.Unsafe (unsafeCrashWith, unsafePartial)
import Simple.JSON as JSON

defaultTxOptions :: TransactionOptions NoPay
defaultTxOptions =
let
defaultGasLimit = unsafePartial fromJust $ parseBigNumber decimal "67123880"

defaultGasPrice = unsafePartial fromJust $ parseBigNumber decimal "10000000000"
in
defaultTransactionOptions
# _gas
?~ defaultGasLimit
# _gasPrice
?~ defaultGasPrice

txOptsWithGasSettings ::
GasSettings ->
TransactionOptions NoPay
txOptsWithGasSettings (GasSettings { gasLimit, gasPrice }) =
defaultTxOptions # _gas .~ maybe (defaultTxOptions ^. _gas) Just gasLimit
# _gasPrice
.~ maybe (defaultTxOptions ^. _gasPrice) Just gasPrice

awaitTxSuccess :: forall m. MonadAff m => HexString -> Provider -> m Unit
awaitTxSuccess txHash provider = do
Expand All @@ -17,3 +49,42 @@ awaitTxSuccess txHash provider = do

awaitTxSuccessWeb3 :: HexString -> Web3 Unit
awaitTxSuccessWeb3 txHash = awaitTxSuccess txHash =<< ask

deployContractWithConfig ::
forall a.
{ gasSettings :: GasSettings
, contractConfig :: ContractConfig a
} ->
DeployM (DeployReceipt a)
deployContractWithConfig { contractConfig, gasSettings } = do
deployCfg@(DeployConfig { primaryAccount, provider }) <- ask
deployContract (txOptsWithGasSettings gasSettings # _from ?~ primaryAccount) contractConfig

newtype GasSettings
= GasSettings
{ gasLimit :: Maybe BigNumber
, gasPrice :: Maybe BigNumber
}

derive newtype instance gasSettingsEq :: Eq GasSettings

derive newtype instance gasSettingsShow :: Show GasSettings

instance gasSettingsReadForeign :: JSON.ReadForeign GasSettings where
readImpl f = do
({ gasLimit, gasPrice } :: { gasLimit :: Maybe String, gasPrice :: Maybe String }) <- JSON.readImpl f
gl <- maybe (pure Nothing) (map Just <<< parseDecimalBigNum "gasLimit") gasLimit
gp <- maybe (pure Nothing) (map Just <<< parseDecimalBigNum "gasPrice") gasPrice
pure $ GasSettings { gasLimit: gl, gasPrice: gp }
where
parseDecimalBigNum name val =
maybe
( throwError $ singleton $ ForeignError
$ "Failed to parse "
<> name
<> ": "
<> show val
<> " as a decimal BigNumber."
)
pure
$ parseBigNumber decimal val
107 changes: 107 additions & 0 deletions purs-contracts/src/Migrations/SuperRareMarketAuctionV2.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
module Migrations.SuperRareMarketAuctionV2 where

import Prelude
import Chanterelle.Internal.Logging (LogLevel(..), log)
import Chanterelle.Internal.Types (DeployConfig(..))
import Contracts.V5.SuperRareMarketAuctionV2 (markTokensAsSold)
import Control.Monad.Reader (ask)
import Data.Array (catMaybes, drop, nub, take, (:))
import Data.Either (Either(..), either)
import Data.Lens ((?~))
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Traversable (for)
import Deploy.Contracts.SuperRareMarketAuctionV2 (deployScriptWithGasSettings)
import Deploy.Utils (GasSettings, awaitTxSuccessWeb3, txOptsWithGasSettings)
import Effect (Effect)
import Effect.Aff (joinFiber, launchAff, runAff_)
import Effect.Aff.Class (class MonadAff, liftAff)
import Effect.Class (liftEffect)
import Effect.Exception (throw, throwException)
import Migrations.Utils (emptyGasSettings, runMigration)
import Network.Ethereum.Web3 (Address, UIntN, _from, _to, embed, runWeb3, uIntNFromBigNumber)
import Network.Ethereum.Web3.Solidity.Sizes (S256, s256)
import Simple.Graphql.Query (runQuery)
import Simple.Graphql.Types (GraphQlQuery(..), runQueryT)

type MigrationArgs
= { superRareV2ContractAddress :: Address
, pixuraApi :: { url :: String, apiKey :: String }
}

main :: Effect Unit
main =
runAff_ (either throwException (const $ log Info "Completed Migration"))
$ runMigration \(args :: { gasSettings :: Maybe GasSettings, migrationArgs :: MigrationArgs }) -> do
DeployConfig { provider, primaryAccount } <- ask
let
{ migrationArgs
, gasSettings: mgs
} = args

{ superRareV2ContractAddress: _originContract
, pixuraApi: { url, apiKey }
} = migrationArgs

gasSettings = fromMaybe emptyGasSettings mgs

txOpts = txOptsWithGasSettings gasSettings # _from ?~ primaryAccount
fibTokens <- liftEffect $ launchAff (lookUpSoldTokens { tokenContract: _originContract, url, apiKey })
{ superRareMarketAuctionV2: { deployAddress } } <- deployScriptWithGasSettings gasSettings
res <-
liftAff
$ runWeb3 provider do
soldTokens <- liftAff $ joinFiber fibTokens
for (chunk 100 soldTokens) \_tokenIds -> do
txHash <-
markTokensAsSold
(txOpts # _to ?~ deployAddress)
{ _originContract, _tokenIds }
log Info $ "Batch marking tokens sold: " <> show _tokenIds
log Info $ "Polling for markTokensAsSold transaction receipt: " <> show txHash
awaitTxSuccessWeb3 txHash
case res of
Left err -> liftEffect $ throw $ show err
_ -> pure unit
where
chunk n [] = []

chunk n xs = take n xs : chunk n (drop n xs)

type LookUpSoldTokensRes
= { allNonFungibleTokenEvents ::
{ nodes :: Array { tokenId :: Int }
}
}

lookUpSoldTokens ::
forall m.
MonadAff m =>
{ tokenContract :: Address, url :: String, apiKey :: String } -> m (Array (UIntN S256))
lookUpSoldTokens { tokenContract, url, apiKey } = do
res <- liftAff $ runQueryT (runQuery url (Just apiKey) gqlQuery)
case res of
{ data: Nothing, errors: Nothing } -> liftEffect $ throw $ "No response for query \n" <> show gqlQuery
{ data: Nothing, errors: Just err } -> liftEffect $ throw $ show err
{ data: Just { allNonFungibleTokenEvents: { nodes } } } ->
pure $ nub $ catMaybes
$ nodes
<#> \{ tokenId } -> uIntNFromBigNumber s256 (embed tokenId)
where
gqlQuery :: GraphQlQuery { contractAddress :: Address } LookUpSoldTokensRes
gqlQuery = GraphQlQuery { query, variables: { contractAddress: tokenContract } }

query =
"""
query getNft($contractAddress: String!) {
allNonFungibleTokenEvents(
orderBy: TIMESTAMP_DESC
condition: { tokenContractAddress: $contractAddress }
filter: { nftEventType: { in: [SALE, ACCEPT_BID] } }
) {
totalCount
nodes {
tokenId
}
}
}
"""
60 changes: 60 additions & 0 deletions purs-contracts/src/Migrations/Utils.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module Migrations.Utils where

import Prelude
import Chanterelle.Deploy (deployWithProvider)
import Chanterelle.Internal.Types (DeployM)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..), maybe)
import Data.Nullable (null)
import Deploy.Utils (GasSettings(..))
import Effect (Effect)
import Effect.Aff (Aff)
import Effect.Class (liftEffect)
import Effect.Exception (throw)
import Network.Ethereum.Web3 (Provider, httpProvider)
import Network.Ethereum.Web3.Types.HdWalletProvider (hdWalletProvider, unHdWalletProvider)
import Node.Encoding (Encoding(..))
import Node.FS.Aff (readTextFile)
import Node.Process (lookupEnv)
import Simple.JSON as JSON

runMigration ::
forall a b.
JSON.ReadForeign b =>
( { migrationArgs :: b
, gasSettings ::
Maybe GasSettings
} ->
DeployM a
) ->
Aff a
runMigration migration = do
config@{ migrationArgs, gasSettings } <- loadMigrationConfig
provider <- liftEffect $ mkProvider config
deployWithProvider provider (60 * 1000) (migration { migrationArgs, gasSettings })

loadMigrationConfig :: forall a. JSON.ReadForeign a => Aff (MigrationConfig a)
loadMigrationConfig = do
cfgPath <- maybe (liftEffect $ throw "`CONFIG` environment variable not found") pure =<< (liftEffect $ lookupEnv "CONFIG")
contents <- readTextFile UTF8 cfgPath
case JSON.readJSON contents of
Left err -> liftEffect $ throw $ show err
Right a -> pure a

emptyGasSettings :: GasSettings
emptyGasSettings = GasSettings { gasPrice: Nothing, gasLimit: Nothing }

mkProvider :: forall a. (MigrationConfig (a)) -> Effect Provider
mkProvider cfg@{ rpcUrl } =
liftEffect case cfg of
{ mnemonic: Just mnemonic } ->
unHdWalletProvider
<$> hdWalletProvider { mnemonic, rpc: rpcUrl, path: null, numberOfAccounts: null }
_ -> httpProvider rpcUrl

type MigrationConfig a
= { rpcUrl :: String
, mnemonic :: Maybe String
, gasSettings :: Maybe GasSettings
, migrationArgs :: a
}
Loading

0 comments on commit 631c219

Please sign in to comment.