├── test ├── fakes │ ├── Hash.abi │ ├── Sig.abi │ ├── Hash.bin │ ├── Sig.bin │ ├── SigFake.sol │ ├── SigFake.abi │ ├── HashFake.abi │ ├── HashFake.sol │ ├── Sig.sol │ ├── Hash.sol │ └── SigFake.bin ├── swivel │ ├── CErc20.bin │ ├── Erc20.bin │ ├── Hash.abi │ ├── Safe.abi │ ├── Sig.abi │ ├── MarketPlace.bin │ ├── Hash.bin │ ├── Safe.bin │ ├── Sig.bin │ ├── CErc20.abi │ ├── Erc20.abi │ ├── Sig.sol │ ├── Interfaces.sol │ ├── Hash.sol │ ├── MarketPlace.abi │ └── Safe.sol ├── tokens │ ├── Hash.abi │ ├── IPErc20.bin │ ├── IErc2612.bin │ ├── IZcToken.bin │ ├── Hash.bin │ ├── IZcToken.sol │ ├── IErc2612.abi │ ├── Underlying.sol │ ├── Underlying.abi │ ├── ZcToken.sol │ ├── Underlying.bin │ ├── IPErc20.abi │ ├── Erc2612.sol │ ├── IZcToken.abi │ ├── IErc2612.sol │ ├── Hash.sol │ ├── PErc20.abi │ ├── IPErc20.sol │ ├── Erc2612.abi │ └── ZcToken.abi ├── marketplace │ ├── CErc20.bin │ ├── Erc20.bin │ ├── Erc20.abi │ ├── CErc20.abi │ ├── Interfaces.sol │ ├── ZcToken.sol │ ├── ZcToken.abi │ ├── VaultTracker.sol │ ├── VaultTracker.bin │ ├── VaultTracker.abi │ └── ZcToken.bin ├── vaulttracker │ ├── CErc20.bin │ ├── CErc20.abi │ ├── Interfaces.sol │ └── VaultTracker.abi └── mocks │ ├── FErc20.sol │ ├── CErc20.abi │ ├── CErc20.sol │ ├── CErc20.bin │ ├── Erc20.sol │ └── Erc20.abi ├── .travis.yml ├── go.mod ├── pkg ├── testing │ ├── constants.go │ ├── env.go │ ├── sig_test.go │ ├── dep.go │ ├── zc_token_test.go │ └── hash_test.go ├── swiveltesting │ ├── constants.go │ ├── env.go │ ├── swivel_construction_test.go │ ├── set_fee_test.go │ ├── approve_underlying_test.go │ ├── transfer_admin_test.go │ ├── dep.go │ ├── redeem_zctoken_test.go │ ├── withdrawal_test.go │ ├── split_and_combine_test.go │ └── redeem_vault_interest_test.go ├── vaulttrackertesting │ ├── constants.go │ ├── dep.go │ ├── mature_vault_test.go │ ├── env.go │ ├── vault_tracker_construction_test.go │ ├── balances_of_test.go │ ├── transfer_notional_fee_test.go │ └── transfer_notional_from_test.go └── marketplacetesting │ ├── constants.go │ ├── construction_test.go │ ├── dep.go │ ├── env.go │ ├── set_swivel_address_test.go │ ├── transfer_admin_test.go │ ├── get_ctoken_address_address_test.go │ ├── transfer_vault_notional_fee_test.go │ ├── transfer_vault_notional_test.go │ ├── p2p_zc_token_exchange_test.go │ └── redeem_vault_interest_test.go ├── .gitignore ├── internal └── helpers │ └── helpers.go └── README.md /test/fakes/Hash.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/fakes/Sig.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/swivel/CErc20.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/swivel/Erc20.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/swivel/Hash.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/swivel/Safe.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/swivel/Sig.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/tokens/Hash.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/tokens/IPErc20.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/marketplace/CErc20.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/marketplace/Erc20.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/swivel/MarketPlace.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/tokens/IErc2612.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/tokens/IZcToken.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/vaulttracker/CErc20.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.15.x 4 | script: go test -v ./... 5 | -------------------------------------------------------------------------------- /test/marketplace/Erc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/swivel-finance/gost 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/ethereum/go-ethereum v1.10.13 7 | github.com/stretchr/testify v1.7.0 8 | ) 9 | -------------------------------------------------------------------------------- /test/vaulttracker/CErc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"exchangeRateCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/vaulttracker/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | interface CErc20 { 6 | function exchangeRateCurrent() external returns (uint256); 7 | } 8 | -------------------------------------------------------------------------------- /test/swivel/Hash.bin: -------------------------------------------------------------------------------- 1 | 60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220073b2931ccc52cd590f2f0c62005f9b123bae3dd8f0e2b18b8350afcf743657a64736f6c634300080d0033 -------------------------------------------------------------------------------- /test/swivel/Safe.bin: -------------------------------------------------------------------------------- 1 | 60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220919b363a1c4b82a544c3539226f72ccc4696533172003af3b569a98209ed25b964736f6c634300080d0033 -------------------------------------------------------------------------------- /test/swivel/Sig.bin: -------------------------------------------------------------------------------- 1 | 60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212207294933ce9d5bce406203ba920eeb1264267bb593bc646a381ecfb990681755964736f6c634300080d0033 -------------------------------------------------------------------------------- /pkg/testing/constants.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import "math/big" 4 | 5 | const ONE_ETH = 1000000000000000000 6 | const ONE_GWEI = 1000000000 7 | const ONE_WEI = 1 8 | 9 | var ZERO = big.NewInt(0) 10 | 11 | // MATURITY is one day, in seconds 12 | const MATURITY = 86400 13 | 14 | var CHAIN_ID = big.NewInt(1337) 15 | -------------------------------------------------------------------------------- /test/marketplace/CErc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"exchangeRateCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"underlying","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/swiveltesting/constants.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import "math/big" 4 | 5 | const ONE_ETH = 1000000000000000000 6 | const ONE_GWEI = 1000000000 7 | const ONE_WEI = 1 8 | 9 | var ZERO = big.NewInt(0) 10 | 11 | // MATURITY is one day, in seconds 12 | const MATURITY = 86400 13 | 14 | var CHAIN_ID = big.NewInt(1337) 15 | -------------------------------------------------------------------------------- /test/fakes/Hash.bin: -------------------------------------------------------------------------------- 1 | 60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220a33fd806486538c36cc22c96e0fe53d5e0b88d26083a69ae5e46b79bf5788b6e64736f6c634300080d0033 -------------------------------------------------------------------------------- /test/fakes/Sig.bin: -------------------------------------------------------------------------------- 1 | 60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122016ab4ad16dd3659762d9b4362f5db7c0be6f91bfbb850dac1f056f2a8c25af3464736f6c634300080d0033 -------------------------------------------------------------------------------- /test/tokens/Hash.bin: -------------------------------------------------------------------------------- 1 | 60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212202f0e0700a91cecb04cb47a7c800212db5699c4b1f768bc09c11a6890867c500f64736f6c634300080d0033 -------------------------------------------------------------------------------- /test/marketplace/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | interface Erc20 { 6 | function decimals() external returns (uint8); 7 | } 8 | 9 | interface CErc20 { 10 | function exchangeRateCurrent() external returns (uint256); 11 | function underlying() external returns (address); 12 | } 13 | -------------------------------------------------------------------------------- /test/swivel/CErc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Ignore vim generated files if present 15 | *.swp 16 | *.swo 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | .idea 22 | .DS_Store -------------------------------------------------------------------------------- /test/tokens/IZcToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | import "./IPErc20.sol"; 6 | 7 | /** 8 | * @dev Mint and burn interface for the ZCToken 9 | * 10 | */ 11 | interface IZcToken is IPErc20 { 12 | /** 13 | * @dev Mints... 14 | */ 15 | function mint(address, uint256) external returns(bool); 16 | 17 | /** 18 | * @dev Burns... 19 | */ 20 | function burn(address, uint256) external returns(bool); 21 | } 22 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/constants.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import "math/big" 4 | 5 | const ONE_ETH = 1000000000000000000 6 | 7 | var ZERO = big.NewInt(0) 8 | 9 | // MATURITY is one day, in seconds 10 | const MATURITY = 86400 11 | 12 | // REDEEM_INTEREST_EVENT_SIG = crypto.Keccak256Hash([]byte("RedeemInterest(address,uint256))").Hex() 13 | const REDEEM_INTEREST_EVENT_SIG = "0x83a945bd12c713615b59a6e48a3467c05d1a7442350600d6f7fce6af9f7190e9" 14 | 15 | var CHAIN_ID = big.NewInt(1337) 16 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/constants.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import "math/big" 4 | 5 | const ONE_ETH = 1000000000000000000 6 | const ONE_GWEI = 1000000000 7 | const ONE_WEI = 1 8 | 9 | var ZERO = big.NewInt(0) 10 | 11 | // MATURITY is one day, in seconds 12 | const MATURITY = 86400 13 | 14 | // MATURE_EVENT_SIG = crypto.Keccak256Hash([]byte("Mature(address,uint256,uint256,uint256))").Hex() 15 | const MATURE_EVENT_SIG = "0x0080e09d7b4544aa5a923873be1df3e31945593d40cb1c874d99259ec3ac43a4" 16 | 17 | var CHAIN_ID = big.NewInt(1337) 18 | -------------------------------------------------------------------------------- /test/fakes/SigFake.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | @dev SigFake.sol is written specfically to test the functions which exist in our Sig.sol "embedded" library 5 | */ 6 | 7 | pragma solidity >= 0.8.4; 8 | 9 | import './Sig.sol'; 10 | 11 | contract SigFake { 12 | function splitTest(bytes memory sig) public pure returns (uint8 v, bytes32 r, bytes32 s) { 13 | return Sig.split(sig); 14 | } 15 | 16 | function recoverTest(bytes32 h, Sig.Components calldata c) public pure returns (address) { 17 | return Sig.recover(h,c); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/tokens/IErc2612.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"o","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"d","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/tokens/Underlying.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | Underlying is a (for now) ERC20 compatible token interface 5 | */ 6 | 7 | pragma solidity >= 0.8.4; 8 | 9 | // For v2 we only need to read the balance and allowance of underlying 10 | contract Underlying { 11 | mapping (address => uint256) public balances; 12 | 13 | mapping (address => mapping (address => uint256)) public allowances; 14 | 15 | function balanceOf(address o) public view returns (uint256) { 16 | return balances[o]; // this should not matter to the abi. i think... 17 | } 18 | 19 | function allowance(address o, address s) public view returns (uint256) { 20 | return allowances[o][s]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/fakes/SigFake.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"bytes32","name":"h","type":"bytes32"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct Sig.Components","name":"c","type":"tuple"}],"name":"recoverTest","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"splitTest","outputs":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"stateMutability":"pure","type":"function"}] -------------------------------------------------------------------------------- /test/mocks/FErc20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | FErc20 is a mock rari token with stubs of the methods we need for testing. 5 | */ 6 | 7 | pragma solidity >= 0.8.4; 8 | 9 | import './Erc20.sol'; 10 | import './CErc20.sol'; 11 | 12 | // TODO this could inherit from the ERC20 mock if needed 13 | contract FErc20 is Erc20, CErc20 { 14 | mapping (address => uint256) public allocateToCalled; 15 | /// @dev allows us to dictate the return from allocateTo calls 16 | bool private allocateToReturn; 17 | 18 | function allocateTo(address o, uint256 a) public returns (bool) { 19 | allocateToCalled[o] = a; 20 | return allocateToReturn; 21 | } 22 | 23 | function allocateToReturns(bool b) public { 24 | allocateToReturn = b; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/tokens/Underlying.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /test/swivel/Erc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/dep.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/swivel-finance/gost/test/mocks" 8 | "github.com/swivel-finance/gost/test/vaulttracker" 9 | ) 10 | 11 | type Dep struct { 12 | CErc20 *mocks.CErc20 13 | CErc20Address common.Address 14 | 15 | SwivelAddress common.Address 16 | 17 | VaultTracker *vaulttracker.VaultTracker 18 | VaultTrackerAddress common.Address 19 | 20 | Maturity *big.Int 21 | } 22 | 23 | func Deploy(e *Env) (*Dep, error) { 24 | maturity := big.NewInt(MATURITY) 25 | cercAddress, _, cercContract, cercErr := mocks.DeployCErc20(e.Owner.Opts, e.Blockchain) 26 | 27 | if cercErr != nil { 28 | return nil, cercErr 29 | } 30 | 31 | e.Blockchain.Commit() 32 | 33 | // vaultTracker expects a swivel address passed to it 34 | swivelAddress := common.HexToAddress("0xAbC123") 35 | 36 | // deploy contract... 37 | trackerAddress, _, trackerContract, trackerErr := vaulttracker.DeployVaultTracker( 38 | e.Owner.Opts, 39 | e.Blockchain, 40 | maturity, 41 | cercAddress, 42 | swivelAddress, 43 | ) 44 | 45 | if trackerErr != nil { 46 | return nil, trackerErr 47 | } 48 | 49 | e.Blockchain.Commit() 50 | 51 | return &Dep{ 52 | SwivelAddress: swivelAddress, 53 | VaultTrackerAddress: trackerAddress, 54 | VaultTracker: trackerContract, 55 | Maturity: maturity, 56 | CErc20: cercContract, 57 | CErc20Address: cercAddress, 58 | }, nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/construction_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | // "github.com/ethereum/go-ethereum/common" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/test/marketplace" 12 | ) 13 | 14 | type marketCtorSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | MarketPlace *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 19 | } 20 | 21 | func (s *marketCtorSuite) SetupSuite() { 22 | var err error 23 | 24 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 25 | s.Dep, err = Deploy(s.Env) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // binding owner to both, kind of why it exists - but could be any of the env wallets 32 | s.MarketPlace = &marketplace.MarketPlaceSession{ 33 | Contract: s.Dep.MarketPlace, 34 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 35 | TransactOpts: bind.TransactOpts{ 36 | From: s.Env.Owner.Opts.From, 37 | Signer: s.Env.Owner.Opts.Signer, 38 | }, 39 | } 40 | } 41 | 42 | func (s *marketCtorSuite) TestAdmin() { 43 | assert := assert.New(s.T()) 44 | addr, err := s.MarketPlace.Admin() 45 | assert.Nil(err) 46 | assert.Equal(addr, s.Env.Owner.Opts.From) 47 | } 48 | 49 | func TestMarketCtorSuite(t *test.T) { 50 | suite.Run(t, &marketCtorSuite{}) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/dep.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/swivel-finance/gost/test/marketplace" 8 | "github.com/swivel-finance/gost/test/mocks" 9 | ) 10 | 11 | type Dep struct { 12 | Erc20 *mocks.Erc20 13 | Erc20Address common.Address 14 | 15 | CErc20 *mocks.CErc20 16 | CErc20Address common.Address 17 | 18 | MarketPlaceAddress common.Address 19 | MarketPlace *marketplace.MarketPlace 20 | 21 | Maturity *big.Int 22 | 23 | SwivelAddress common.Address 24 | } 25 | 26 | func Deploy(e *Env) (*Dep, error) { 27 | maturity := big.NewInt(MATURITY) 28 | cercAddress, _, cercContract, cercErr := mocks.DeployCErc20(e.Owner.Opts, e.Blockchain) 29 | 30 | if cercErr != nil { 31 | return nil, cercErr 32 | } 33 | 34 | e.Blockchain.Commit() 35 | 36 | ercAddress, _, ercContract, ercErr := mocks.DeployErc20(e.Owner.Opts, e.Blockchain) 37 | 38 | if ercErr != nil { 39 | return nil, ercErr 40 | } 41 | 42 | e.Blockchain.Commit() 43 | 44 | // deploy contract... 45 | marketAddress, _, marketContract, marketErr := marketplace.DeployMarketPlace(e.Owner.Opts, e.Blockchain) 46 | 47 | if marketErr != nil { 48 | return nil, marketErr 49 | } 50 | 51 | e.Blockchain.Commit() 52 | 53 | return &Dep{ 54 | MarketPlaceAddress: marketAddress, 55 | MarketPlace: marketContract, 56 | CErc20Address: cercAddress, 57 | CErc20: cercContract, 58 | Erc20Address: ercAddress, 59 | Erc20: ercContract, 60 | SwivelAddress: common.HexToAddress("0xAbC123"), 61 | Maturity: maturity, 62 | }, nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/mature_vault_test.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/vaulttracker" 11 | ) 12 | 13 | type matureVaultSuite struct { 14 | suite.Suite 15 | Env *Env 16 | Dep *Dep 17 | VaultTracker *vaulttracker.VaultTrackerSession // *Session objects are created by the go bindings 18 | } 19 | 20 | func (s *matureVaultSuite) SetupTest() { 21 | var err error 22 | 23 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 24 | s.Dep, err = Deploy(s.Env) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | // binding owner to both, kind of why it exists - but could be any of the env wallets 30 | s.VaultTracker = &vaulttracker.VaultTrackerSession{ 31 | Contract: s.Dep.VaultTracker, 32 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 33 | TransactOpts: bind.TransactOpts{ 34 | From: s.Env.Owner.Opts.From, 35 | Signer: s.Env.Owner.Opts.Signer, 36 | }, 37 | } 38 | } 39 | 40 | func (s *matureVaultSuite) TestMatureVault() { 41 | assert := assertions.New(s.T()) 42 | 43 | rate1 := big.NewInt(123456789) 44 | 45 | tx, err := s.VaultTracker.MatureVault(rate1) 46 | assert.Nil(err) 47 | assert.NotNil(tx) 48 | 49 | s.Env.Blockchain.Commit() 50 | 51 | rate2, err := s.VaultTracker.MaturityRate() 52 | assert.Nil(err) 53 | assert.Equal(rate2, rate1) 54 | } 55 | 56 | func TestTrackerMatureVaultSuite(t *test.T) { 57 | suite.Run(t, &matureVaultSuite{}) 58 | } 59 | -------------------------------------------------------------------------------- /test/tokens/ZcToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // NOTE: whomever decided methods which are the implementation of a stated interface 4 | // need to be labeled 'override' should to be publicly flogged. 5 | 6 | pragma solidity >= 0.8.4; 7 | 8 | import './Erc2612.sol'; 9 | import './IZcToken.sol'; 10 | 11 | /// NOTE the OZStlye naming conventions are kept for the internal methods 12 | /// _burn and _mint as dangling underscores are generally not allowed. 13 | contract ZcToken is Erc2612, IZcToken { 14 | address public immutable admin; 15 | address public immutable underlying; 16 | uint256 public immutable maturity; 17 | 18 | /// @param u Underlying 19 | /// @param m Maturity 20 | /// @param n Name 21 | /// @param s Symbol 22 | /// @param d Decimals 23 | constructor(address u, uint256 m, string memory n, string memory s, uint8 d) Erc2612(n, s, d) { 24 | admin = msg.sender; 25 | underlying = u; 26 | maturity = m; 27 | } 28 | 29 | /// @param f Address to burn from 30 | /// @param a Amount to burn 31 | function burn(address f, uint256 a) external onlyAdmin(admin) override returns(bool) { 32 | _burn(f, a); 33 | return true; 34 | } 35 | 36 | /// @dev Muturity must not have been reached else we revert here. 37 | /// @param t Address recieving the minted amount 38 | /// @param a The amount to mint 39 | function mint(address t, uint256 a) external onlyAdmin(admin) override returns(bool) { 40 | require(block.timestamp < maturity, 'maturity reached'); 41 | _mint(t, a); 42 | return true; 43 | } 44 | 45 | /// @param a Admin address 46 | modifier onlyAdmin(address a) { 47 | require(msg.sender == a, 'sender must be admin'); 48 | _; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/tokens/Underlying.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b50610357806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806327e235e31461005157806355b6ed5c1461008157806370a08231146100b1578063dd62ed3e146100e1575b600080fd5b61006b60048036038101906100669190610280565b610111565b60405161007891906102c6565b60405180910390f35b61009b600480360381019061009691906102e1565b610129565b6040516100a891906102c6565b60405180910390f35b6100cb60048036038101906100c69190610280565b61014e565b6040516100d891906102c6565b60405180910390f35b6100fb60048036038101906100f691906102e1565b610196565b60405161010891906102c6565b60405180910390f35b60006020528060005260406000206000915090505481565b6001602052816000526040600020602052806000526040600020600091509150505481565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061024d82610222565b9050919050565b61025d81610242565b811461026857600080fd5b50565b60008135905061027a81610254565b92915050565b6000602082840312156102965761029561021d565b5b60006102a48482850161026b565b91505092915050565b6000819050919050565b6102c0816102ad565b82525050565b60006020820190506102db60008301846102b7565b92915050565b600080604083850312156102f8576102f761021d565b5b60006103068582860161026b565b92505060206103178582860161026b565b915050925092905056fea2646970667358221220855bf88dd4a079562c6bb539f09df48d2d8024e424c9b3f7282a4b0371dc35a764736f6c634300080d0033 -------------------------------------------------------------------------------- /test/fakes/HashFake.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"string","name":"n","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"c","type":"uint256"},{"internalType":"address","name":"verifier","type":"address"}],"name":"domainTest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"domainTypeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"d","type":"bytes32"},{"internalType":"bytes32","name":"h","type":"bytes32"}],"name":"messageTest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"bool","name":"vault","type":"bool"},{"internalType":"bool","name":"exit","type":"bool"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"premium","type":"uint256"},{"internalType":"uint256","name":"maturity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"}],"internalType":"struct Hash.Order","name":"o","type":"tuple"}],"name":"orderTest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"orderTypeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"permitTypeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"}] -------------------------------------------------------------------------------- /pkg/testing/env.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" 9 | "github.com/ethereum/go-ethereum/core" 10 | "github.com/swivel-finance/gost/internal/helpers" 11 | ) 12 | 13 | // Auth is a custom type which allows us easy access to the dynamically generated 14 | // ecdsa.PrivateKey along with the bind.TransactOpts 15 | type Auth struct { 16 | PK *ecdsa.PrivateKey 17 | Opts *bind.TransactOpts 18 | } 19 | 20 | // Env, holds Auth objects capable of signing transactions. 21 | // Also holds the Geth simulated backend. 22 | type Env struct { 23 | Alloc core.GenesisAlloc 24 | // TODO maybe change to Admin to fit v2 contract terms... 25 | Owner *Auth 26 | User1 *Auth 27 | User2 *Auth 28 | Blockchain *backends.SimulatedBackend 29 | } 30 | 31 | // NewEnv returns a hydrated Env struct, ready for use. 32 | // Given a balance argument, it assigns this as the wallet balance for 33 | // each authorization object in the Ctx 34 | func NewEnv(b *big.Int) *Env { 35 | pk, owner := helpers.NewAuth(CHAIN_ID) 36 | pk1, u1 := helpers.NewAuth(CHAIN_ID) 37 | pk2, u2 := helpers.NewAuth(CHAIN_ID) 38 | alloc := make(core.GenesisAlloc) 39 | alloc[owner.From] = core.GenesisAccount{Balance: b} 40 | alloc[u1.From] = core.GenesisAccount{Balance: b} 41 | alloc[u2.From] = core.GenesisAccount{Balance: b} 42 | // 2nd arg is a gas limit, a uint64. we'll use 4.7 million 43 | bc := backends.NewSimulatedBackend(alloc, 4700000) 44 | 45 | return &Env{ 46 | Alloc: alloc, 47 | Owner: &Auth{PK: pk, Opts: owner}, 48 | User1: &Auth{PK: pk1, Opts: u1}, 49 | User2: &Auth{PK: pk2, Opts: u2}, 50 | Blockchain: bc, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/swiveltesting/env.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" 9 | "github.com/ethereum/go-ethereum/core" 10 | "github.com/swivel-finance/gost/internal/helpers" 11 | ) 12 | 13 | // Auth is a custom type which allows us easy access to the dynamically generated 14 | // ecdsa.PrivateKey along with the bind.TransactOpts 15 | type Auth struct { 16 | PK *ecdsa.PrivateKey 17 | Opts *bind.TransactOpts 18 | } 19 | 20 | // Env, holds Auth objects capable of signing transactions. 21 | // Also holds the Geth simulated backend. 22 | type Env struct { 23 | Alloc core.GenesisAlloc 24 | // TODO maybe change to Admin to fit v2 contract terms... 25 | Owner *Auth 26 | User1 *Auth 27 | User2 *Auth 28 | Blockchain *backends.SimulatedBackend 29 | } 30 | 31 | // NewEnv returns a hydrated Env struct, ready for use. 32 | // Given a balance argument, it assigns this as the wallet balance for 33 | // each authorization object in the Ctx 34 | func NewEnv(b *big.Int) *Env { 35 | pk, owner := helpers.NewAuth(CHAIN_ID) 36 | pk1, u1 := helpers.NewAuth(CHAIN_ID) 37 | pk2, u2 := helpers.NewAuth(CHAIN_ID) 38 | alloc := make(core.GenesisAlloc) 39 | alloc[owner.From] = core.GenesisAccount{Balance: b} 40 | alloc[u1.From] = core.GenesisAccount{Balance: b} 41 | alloc[u2.From] = core.GenesisAccount{Balance: b} 42 | // 2nd arg is a gas limit, a uint64. we'll use... 43 | bc := backends.NewSimulatedBackend(alloc, 5700000) 44 | 45 | return &Env{ 46 | Alloc: alloc, 47 | Owner: &Auth{PK: pk, Opts: owner}, 48 | User1: &Auth{PK: pk1, Opts: u1}, 49 | User2: &Auth{PK: pk2, Opts: u2}, 50 | Blockchain: bc, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/tokens/IPErc20.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/marketplacetesting/env.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" 9 | "github.com/ethereum/go-ethereum/core" 10 | "github.com/swivel-finance/gost/internal/helpers" 11 | ) 12 | 13 | // Auth is a custom type which allows us easy access to the dynamically generated 14 | // ecdsa.PrivateKey along with the bind.TransactOpts 15 | type Auth struct { 16 | PK *ecdsa.PrivateKey 17 | Opts *bind.TransactOpts 18 | } 19 | 20 | // Env, holds Auth objects capable of signing transactions. 21 | // Also holds the Geth simulated backend. 22 | type Env struct { 23 | Alloc core.GenesisAlloc 24 | // TODO maybe change to Admin to fit v2 contract terms... 25 | Owner *Auth 26 | User1 *Auth 27 | User2 *Auth 28 | Blockchain *backends.SimulatedBackend 29 | } 30 | 31 | // NewEnv returns a hydrated Env struct, ready for use. 32 | // Given a balance argument, it assigns this as the wallet balance for 33 | // each authorization object in the Ctx 34 | func NewEnv(b *big.Int) *Env { 35 | pk, owner := helpers.NewAuth(CHAIN_ID) 36 | pk1, u1 := helpers.NewAuth(CHAIN_ID) 37 | pk2, u2 := helpers.NewAuth(CHAIN_ID) 38 | alloc := make(core.GenesisAlloc) 39 | alloc[owner.From] = core.GenesisAccount{Balance: b} 40 | alloc[u1.From] = core.GenesisAccount{Balance: b} 41 | alloc[u2.From] = core.GenesisAccount{Balance: b} 42 | // 2nd arg is a gas limit, a uint64. we'll use... 43 | bc := backends.NewSimulatedBackend(alloc, 6700000) 44 | 45 | return &Env{ 46 | Alloc: alloc, 47 | Owner: &Auth{PK: pk, Opts: owner}, 48 | User1: &Auth{PK: pk1, Opts: u1}, 49 | User2: &Auth{PK: pk2, Opts: u2}, 50 | Blockchain: bc, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/env.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" 9 | "github.com/ethereum/go-ethereum/core" 10 | "github.com/swivel-finance/gost/internal/helpers" 11 | ) 12 | 13 | // Auth is a custom type which allows us easy access to the dynamically generated 14 | // ecdsa.PrivateKey along with the bind.TransactOpts 15 | type Auth struct { 16 | PK *ecdsa.PrivateKey 17 | Opts *bind.TransactOpts 18 | } 19 | 20 | // Env, holds Auth objects capable of signing transactions. 21 | // Also holds the Geth simulated backend. 22 | type Env struct { 23 | Alloc core.GenesisAlloc 24 | // TODO maybe change to Admin to fit v2 contract terms... 25 | Owner *Auth 26 | User1 *Auth 27 | User2 *Auth 28 | Blockchain *backends.SimulatedBackend 29 | } 30 | 31 | // NewEnv returns a hydrated Env struct, ready for use. 32 | // Given a balance argument, it assigns this as the wallet balance for 33 | // each authorization object in the Ctx 34 | func NewEnv(b *big.Int) *Env { 35 | pk, owner := helpers.NewAuth(CHAIN_ID) 36 | pk1, u1 := helpers.NewAuth(CHAIN_ID) 37 | pk2, u2 := helpers.NewAuth(CHAIN_ID) 38 | alloc := make(core.GenesisAlloc) 39 | alloc[owner.From] = core.GenesisAccount{Balance: b} 40 | alloc[u1.From] = core.GenesisAccount{Balance: b} 41 | alloc[u2.From] = core.GenesisAccount{Balance: b} 42 | // 2nd arg is a gas limit, a uint64. we'll use 4.7 million 43 | bc := backends.NewSimulatedBackend(alloc, 4700000) 44 | 45 | return &Env{ 46 | Alloc: alloc, 47 | Owner: &Auth{PK: pk, Opts: owner}, 48 | User1: &Auth{PK: pk1, Opts: u1}, 49 | User2: &Auth{PK: pk2, Opts: u2}, 50 | Blockchain: bc, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/swiveltesting/swivel_construction_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | // "github.com/ethereum/go-ethereum/common" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/test/swivel" 12 | ) 13 | 14 | type swivelCtorSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Swivel *swivel.SwivelSession // *Session objects are created by the go bindings 19 | } 20 | 21 | func (s *swivelCtorSuite) SetupSuite() { 22 | var err error 23 | 24 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 25 | s.Dep, err = Deploy(s.Env) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // binding owner to both, kind of why it exists - but could be any of the env wallets 32 | s.Swivel = &swivel.SwivelSession{ 33 | Contract: s.Dep.Swivel, 34 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 35 | TransactOpts: bind.TransactOpts{ 36 | From: s.Env.Owner.Opts.From, 37 | Signer: s.Env.Owner.Opts.Signer, 38 | }, 39 | } 40 | } 41 | 42 | func (s *swivelCtorSuite) TestName() { 43 | assert := assert.New(s.T()) 44 | name, err := s.Swivel.NAME() 45 | assert.Nil(err) 46 | assert.Equal(name, "Swivel Finance") 47 | } 48 | 49 | func (s *swivelCtorSuite) TestVersion() { 50 | assert := assert.New(s.T()) 51 | verz, err := s.Swivel.VERSION() 52 | assert.Nil(err) 53 | assert.Equal(verz, "2.0.0") 54 | } 55 | 56 | func (s *swivelCtorSuite) TestDomain() { 57 | assert := assert.New(s.T()) 58 | separator, err := s.Swivel.Domain() 59 | assert.Nil(err) 60 | assert.Equal(32, len(separator)) 61 | } 62 | 63 | func TestSwivelCtorSuite(t *test.T) { 64 | suite.Run(t, &swivelCtorSuite{}) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/set_swivel_address_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/test/marketplace" 12 | ) 13 | 14 | type swivelAddrSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | MarketPlace *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 19 | } 20 | 21 | func (s *swivelAddrSuite) SetupSuite() { 22 | var err error 23 | 24 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 25 | s.Dep, err = Deploy(s.Env) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | s.MarketPlace = &marketplace.MarketPlaceSession{ 32 | Contract: s.Dep.MarketPlace, 33 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 34 | TransactOpts: bind.TransactOpts{ 35 | From: s.Env.Owner.Opts.From, 36 | Signer: s.Env.Owner.Opts.Signer, 37 | }, 38 | } 39 | } 40 | 41 | func (s *swivelAddrSuite) TestSetSwivelAddr() { 42 | assert := assert.New(s.T()) 43 | bsAddr := common.HexToAddress("0x123456789") 44 | tx, err := s.MarketPlace.SetSwivelAddress(bsAddr) 45 | assert.Nil(err) 46 | assert.NotNil(tx) 47 | 48 | s.Env.Blockchain.Commit() 49 | 50 | addr, _ := s.MarketPlace.Swivel() 51 | assert.Equal(bsAddr, addr) 52 | } 53 | 54 | func (s *swivelAddrSuite) TestSetSwivelAddrFails() { 55 | assert := assert.New(s.T()) 56 | bsAddr := common.HexToAddress("0x123456789") 57 | tx, err := s.MarketPlace.SetSwivelAddress(bsAddr) 58 | assert.Nil(tx) 59 | assert.NotNil(err) 60 | 61 | assert.Regexp("already set", err.Error()) 62 | } 63 | 64 | func TestSwivelAddrSuite(t *test.T) { 65 | suite.Run(t, &swivelAddrSuite{}) 66 | } 67 | -------------------------------------------------------------------------------- /test/mocks/CErc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"exchangeRateCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"exchangeRateCurrentReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"mintReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"redeemUnderlyingCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"redeemUnderlyingReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"supplyRatePerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"supplyRatePerBlockReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"underlying","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"underlyingReturns","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/testing/sig_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/crypto" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | type sigTestSuite struct { 13 | suite.Suite 14 | Env *Env 15 | Dep *Dep 16 | } 17 | 18 | // SetupSuite serves as a 'beforeAll', hydrating both the Env and Dep objects 19 | func (s *sigTestSuite) SetupSuite() { 20 | var err error 21 | 22 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 23 | s.Dep, err = Deploy(s.Env) 24 | 25 | if err != nil { 26 | panic(err) 27 | } 28 | } 29 | 30 | func (s *sigTestSuite) TestSplit() { 31 | assert := assert.New(s.T()) 32 | 33 | msg := []byte("Yo Dawg, heard u liek unit tests") 34 | hash := crypto.Keccak256Hash(msg) 35 | 36 | // sign with user1... 37 | sig, err := crypto.Sign(hash.Bytes(), s.Env.User1.PK) 38 | 39 | assert.Nil(err) 40 | assert.NotNil(sig) 41 | 42 | vrs, err := s.Dep.SigFake.SplitTest(nil, sig) 43 | 44 | assert.Nil(err) 45 | assert.NotNil(vrs) 46 | } 47 | 48 | func (s *sigTestSuite) TestRecover() { 49 | assert := assert.New(s.T()) 50 | 51 | msg := []byte("So we put tests in your tests so you can test while you test") 52 | hash := crypto.Keccak256Hash(msg) 53 | 54 | // sign with user1... 55 | sig, err := crypto.Sign(hash.Bytes(), s.Env.User1.PK) 56 | // the go bindings return a struct here -> { V: R: S: } 57 | vrs, err := s.Dep.SigFake.SplitTest(nil, sig) 58 | 59 | // crypto.Sign will produce a split whose V is 0 or 1 60 | // NOTE: 27 or 28 are acceptable 61 | if vrs.V < 27 { 62 | vrs.V += 27 63 | } 64 | 65 | addr, err := s.Dep.SigFake.RecoverTest(nil, hash, vrs) 66 | 67 | assert.Nil(err) 68 | assert.NotNil(addr) 69 | assert.Equal(addr, s.Env.User1.Opts.From) 70 | } 71 | 72 | func TestSigSuite(t *test.T) { 73 | suite.Run(t, &sigTestSuite{}) 74 | } 75 | -------------------------------------------------------------------------------- /test/fakes/HashFake.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | @dev HashFake.sol is written specfically to test the functions which exist in our Hash.sol "embedded" library 5 | */ 6 | 7 | pragma solidity >= 0.8.4; 8 | 9 | import './Hash.sol'; 10 | 11 | contract HashFake { 12 | /// @dev convenience method to get the domain type hash 13 | function domainTypeHash() public pure returns (bytes32) { 14 | return keccak256(abi.encodePacked( 15 | 'EIP712Domain(', 16 | 'string name,', 17 | 'string version,', 18 | 'uint256 chainId,', 19 | 'address verifyingContract', 20 | ')' 21 | )); 22 | } 23 | 24 | function domainTest(string memory n, string memory version, uint256 c, address verifier) public pure returns (bytes32) { 25 | return Hash.domain(n, version, c, verifier); 26 | } 27 | 28 | function messageTest(bytes32 d, bytes32 h) public pure returns (bytes32) { 29 | return Hash.message(d, h); 30 | } 31 | 32 | /// @dev convenience method to get the order type hash 33 | function orderTypeHash() public pure returns (bytes32) { 34 | return keccak256(abi.encodePacked( 35 | 'Order(', 36 | 'bytes32 key,', 37 | 'address maker,', 38 | 'address underlying,', 39 | 'bool vault,', 40 | 'bool exit,', 41 | 'uint256 principal,', 42 | 'uint256 premium,', 43 | 'uint256 maturity,', 44 | 'uint256 expiry', 45 | ')' 46 | )); 47 | } 48 | 49 | function orderTest(Hash.Order calldata o) external pure returns (bytes32) { 50 | return Hash.order(o); 51 | } 52 | 53 | /// @dev convenience method to generate the /token/hash permit type hash 54 | function permitTypeHash() public pure returns (bytes32) { 55 | return keccak256(abi.encodePacked( 56 | 'Permit(', 57 | 'address owner,', 58 | 'address spender,', 59 | 'uint256 value,', 60 | 'uint256 nonce,', 61 | 'uint256 deadline,', 62 | ')' 63 | )); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/fakes/Sig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | library Sig { 6 | /// @dev ECDSA V,R and S components encapsulated here as we may not always be able to accept a bytes signature 7 | struct Components { 8 | uint8 v; 9 | bytes32 r; 10 | bytes32 s; 11 | } 12 | 13 | /// @param h Hashed data which was originally signed 14 | /// @param c signature struct containing V,R and S 15 | /// @return The recovered address 16 | function recover(bytes32 h, Components calldata c) internal pure returns (address) { 17 | // EIP-2 and malleable signatures... 18 | // see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol 19 | require(uint256(c.s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, 'invalid signature "s" value'); 20 | require(c.v == 27 || c.v == 28, 'invalid signature "v" value'); 21 | 22 | return ecrecover(h, c.v, c.r, c.s); 23 | } 24 | 25 | /// @param h Hashed data which was originally signed 26 | /// @param sig Valid ECDSA signature 27 | /// @dev splitAndRecover should only be used if it is known that the resulting 28 | /// verifying bit (V) will be 27 || 28. Otherwise use recover, possibly calling split first. 29 | /// @return The recovered address 30 | function splitAndRecover(bytes32 h, bytes memory sig) internal pure returns (address) { 31 | (uint8 v, bytes32 r, bytes32 s) = split(sig); 32 | 33 | return ecrecover(h, v, r, s); 34 | } 35 | 36 | /// @param sig Valid ECDSA signature 37 | /// @return v The verification bit 38 | /// @return r First 32 bytes 39 | /// @return s Next 32 bytes 40 | function split(bytes memory sig) internal pure returns (uint8, bytes32, bytes32) { 41 | require(sig.length == 65, 'invalid signature length'); 42 | 43 | bytes32 r; 44 | bytes32 s; 45 | uint8 v; 46 | 47 | assembly { 48 | r := mload(add(sig, 32)) 49 | s := mload(add(sig, 64)) 50 | v := byte(0, mload(add(sig, 96))) 51 | } 52 | 53 | return (v, r, s); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/swivel/Sig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | library Sig { 6 | /// @dev ECDSA V,R and S components encapsulated here as we may not always be able to accept a bytes signature 7 | struct Components { 8 | uint8 v; 9 | bytes32 r; 10 | bytes32 s; 11 | } 12 | 13 | /// @param h Hashed data which was originally signed 14 | /// @param c signature struct containing V,R and S 15 | /// @return The recovered address 16 | function recover(bytes32 h, Components calldata c) internal pure returns (address) { 17 | // EIP-2 and malleable signatures... 18 | // see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol 19 | require(uint256(c.s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, 'invalid signature "s" value'); 20 | require(c.v == 27 || c.v == 28, 'invalid signature "v" value'); 21 | 22 | return ecrecover(h, c.v, c.r, c.s); 23 | } 24 | 25 | /// @param h Hashed data which was originally signed 26 | /// @param sig Valid ECDSA signature 27 | /// @dev splitAndRecover should only be used if it is known that the resulting 28 | /// verifying bit (V) will be 27 || 28. Otherwise use recover, possibly calling split first. 29 | /// @return The recovered address 30 | function splitAndRecover(bytes32 h, bytes memory sig) internal pure returns (address) { 31 | (uint8 v, bytes32 r, bytes32 s) = split(sig); 32 | 33 | return ecrecover(h, v, r, s); 34 | } 35 | 36 | /// @param sig Valid ECDSA signature 37 | /// @return v The verification bit 38 | /// @return r First 32 bytes 39 | /// @return s Next 32 bytes 40 | function split(bytes memory sig) internal pure returns (uint8, bytes32, bytes32) { 41 | require(sig.length == 65, 'invalid signature length'); 42 | 43 | bytes32 r; 44 | bytes32 s; 45 | uint8 v; 46 | 47 | assembly { 48 | r := mload(add(sig, 32)) 49 | s := mload(add(sig, 64)) 50 | v := byte(0, mload(add(sig, 96))) 51 | } 52 | 53 | return (v, r, s); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/mocks/CErc20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | CErc20 is a mock compound token with stubs of the methods we need for testing. 5 | */ 6 | 7 | pragma solidity >= 0.8.4; 8 | 9 | // TODO this could inherit from the ERC20 mock if needed 10 | contract CErc20 { 11 | /// @dev allows us to dictate return from mint(). 12 | uint256 private mintReturn; 13 | /// @dev the last amount mint was called with 14 | uint256 public mintCalled; 15 | /// @dev allows us to dictate return from redeemUnderlying(). 16 | uint256 private redeemUnderlyingReturn; 17 | /// @dev the last amount redeemUnderlying was called with 18 | uint256 public redeemUnderlyingCalled; 19 | /// @dev allows us to dictate return from exchangeRateCurrent(). 20 | address private underlyingReturn; 21 | /// @dev allows us to dictate return from exchangeRateCurrent(). 22 | uint256 private exchangeRateCurrentReturn; 23 | uint256 private supplyRatePerBlockReturn; 24 | 25 | function mint(uint256 n) public returns (uint256) { 26 | mintCalled = n; 27 | return mintReturn; 28 | } 29 | 30 | function mintReturns(uint256 n) public { 31 | mintReturn = n; 32 | } 33 | 34 | function redeemUnderlying(uint256 n) public returns (uint256) { 35 | redeemUnderlyingCalled = n; 36 | return redeemUnderlyingReturn; 37 | } 38 | 39 | function redeemUnderlyingReturns(uint256 n) public { 40 | redeemUnderlyingReturn = n; 41 | } 42 | 43 | function underlying() public view returns (address) { 44 | return underlyingReturn; 45 | } 46 | 47 | function underlyingReturns(address a) public { 48 | underlyingReturn = a; 49 | } 50 | 51 | function exchangeRateCurrent() public view returns (uint256) { 52 | return exchangeRateCurrentReturn; 53 | } 54 | 55 | function exchangeRateCurrentReturns(uint256 n) public { 56 | exchangeRateCurrentReturn = n; 57 | } 58 | 59 | function supplyRatePerBlock() public view returns (uint256) { 60 | return supplyRatePerBlockReturn; 61 | } 62 | 63 | function supplyRatePerBlockReturns(uint256 n) public { 64 | supplyRatePerBlockReturn = n; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pkg/swiveltesting/set_fee_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | test "testing" 7 | 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/test/swivel" 12 | ) 13 | 14 | type setFeeSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Swivel *swivel.SwivelSession // *Session objects are created by the go bindings 19 | } 20 | 21 | func (s *setFeeSuite) SetupSuite() { 22 | var err error 23 | 24 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 25 | s.Dep, err = Deploy(s.Env) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // binding owner to both, kind of why it exists - but could be any of the env wallets 32 | s.Swivel = &swivel.SwivelSession{ 33 | Contract: s.Dep.Swivel, 34 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 35 | TransactOpts: bind.TransactOpts{ 36 | From: s.Env.Owner.Opts.From, 37 | Signer: s.Env.Owner.Opts.Signer, 38 | }, 39 | } 40 | } 41 | 42 | func (s *setFeeSuite) TestSetFee() { 43 | assert := assert.New(s.T()) 44 | 45 | // inspect the original fee first 46 | feenominator, err := s.Swivel.Feenominators(big.NewInt(1)) 47 | assert.Nil(err) 48 | assert.Equal(feenominator, uint16(600)) 49 | 50 | tx, err := s.Swivel.SetFee(uint16(1), uint16(500)) 51 | assert.Nil(err) 52 | assert.NotNil(tx) 53 | 54 | s.Env.Blockchain.Commit() 55 | 56 | // feenominator should have changed... 57 | feenominator, err = s.Swivel.Feenominators(big.NewInt(1)) 58 | assert.Nil(err) 59 | assert.Equal(feenominator, uint16(500)) 60 | } 61 | 62 | func (s *setFeeSuite) TestSetFeeFails() { 63 | assert := assert.New(s.T()) 64 | 65 | // any feenominator lower than 33 should revert 66 | tx, err := s.Swivel.SetFee(uint16(2), uint16(25)) 67 | assert.Nil(tx) 68 | assert.NotNil(err) 69 | assert.True(strings.Contains(err.Error(), "fee too high")) 70 | } 71 | 72 | func TestSetFeeSuite(t *test.T) { 73 | suite.Run(t, &setFeeSuite{}) 74 | } 75 | -------------------------------------------------------------------------------- /test/tokens/Erc2612.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Adapted from OpenZeppelin ERC2612 (ERC20Permit) 3 | 4 | pragma solidity >= 0.8.4; 5 | 6 | import './Hash.sol'; 7 | import './PErc20.sol'; 8 | import './IErc2612.sol'; 9 | 10 | /** 11 | * @dev Extension of {ERC20} that allows token holders to use their tokens 12 | * without sending any transactions by setting {IERC20-allowance} with a 13 | * signature using the {permit} method, and then spend them via 14 | * {IERC20-transferFrom}. 15 | * NOTE: Naming convention is kept OZStyle vs our own OzStyle to prevent clashing 16 | * 17 | * The {permit} signature mechanism conforms to the {IERC2612} interface. 18 | */ 19 | contract Erc2612 is PErc20, IErc2612 { 20 | mapping (address => uint256) public override nonces; 21 | 22 | bytes32 public immutable domain; 23 | 24 | /// @param n name for the token 25 | /// @param s symbol for the token 26 | /// @param d decimals for the token 27 | constructor(string memory n, string memory s, uint8 d) PErc20(n, s, d) { 28 | domain = Hash.domain(n, '1', block.chainid, address(this)); 29 | } 30 | 31 | /** 32 | * @dev See {IERC2612-permit}. 33 | * 34 | * In cases where the free option is not a concern, deadline can simply be 35 | * set to uint(-1), so it should be seen as an optional parameter 36 | * 37 | * @param o Address of the owner 38 | * @param spender Address of the spender 39 | * @param a Amount to be approved 40 | * @param d Deadline at which the permission is no longer valid 41 | * NOTE: Last three args (v, r, s) are the components of a valid ECDSA signature 42 | */ 43 | function permit(address o, address spender, uint256 a, uint256 d, uint8 v, bytes32 r, bytes32 s) public virtual override { 44 | require(d >= block.timestamp, 'erc2612 expired deadline'); 45 | 46 | bytes32 hashStruct = Hash.permit(o, spender, a, nonces[o]++, d); 47 | bytes32 hash = Hash.message(domain, hashStruct); 48 | address signer = ecrecover(hash, v, r, s); 49 | 50 | require(signer != address(0) && signer == o, 'erc2612 invalid signature'); 51 | _approve(o, spender, a); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/vault_tracker_construction_test.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/vaulttracker" 11 | ) 12 | 13 | type vaultTrackerCtorSuite struct { 14 | suite.Suite 15 | Env *Env 16 | Dep *Dep 17 | VaultTracker *vaulttracker.VaultTrackerSession // *Session objects are created by the go bindings 18 | } 19 | 20 | func (s *vaultTrackerCtorSuite) SetupSuite() { 21 | var err error 22 | 23 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 24 | s.Dep, err = Deploy(s.Env) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 30 | if err != nil { 31 | panic(err) 32 | } 33 | s.Env.Blockchain.Commit() 34 | 35 | // binding owner to both, kind of why it exists - but could be any of the env wallets 36 | s.VaultTracker = &vaulttracker.VaultTrackerSession{ 37 | Contract: s.Dep.VaultTracker, 38 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 39 | TransactOpts: bind.TransactOpts{ 40 | From: s.Env.Owner.Opts.From, 41 | Signer: s.Env.Owner.Opts.Signer, 42 | }, 43 | } 44 | } 45 | 46 | func (s *vaultTrackerCtorSuite) TestAdmin() { 47 | assert := assertions.New(s.T()) 48 | addr, err := s.VaultTracker.Admin() 49 | assert.Nil(err) 50 | assert.Equal(addr, s.Env.Owner.Opts.From) 51 | } 52 | 53 | func (s *vaultTrackerCtorSuite) TestCTokenAddress() { 54 | assert := assertions.New(s.T()) 55 | addr, err := s.VaultTracker.CTokenAddr() 56 | assert.Nil(err) 57 | assert.Equal(s.Dep.CErc20Address, addr) 58 | } 59 | 60 | func (s *vaultTrackerCtorSuite) TestMaturity() { 61 | assert := assertions.New(s.T()) 62 | maturity, err := s.VaultTracker.Maturity() 63 | assert.Nil(err) 64 | assert.Equal(maturity, s.Dep.Maturity) 65 | } 66 | 67 | func TestVaultTrackerCtorSuite(t *test.T) { 68 | suite.Run(t, &vaultTrackerCtorSuite{}) 69 | } 70 | -------------------------------------------------------------------------------- /test/swivel/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | interface Erc20 { 6 | function approve(address, uint256) external returns (bool); 7 | function transfer(address, uint256) external returns (bool); 8 | function balanceOf(address) external returns (uint256); 9 | function transferFrom(address, address, uint256) external returns (bool); 10 | } 11 | 12 | interface CErc20 { 13 | function mint(uint256) external returns (uint256); 14 | function redeemUnderlying(uint256) external returns (uint256); 15 | } 16 | 17 | interface MarketPlace { 18 | // adds notional and mints zctokens 19 | function mintZcTokenAddingNotional(address, uint256, address, uint256) external returns (bool); 20 | // removes notional and burns zctokens 21 | function burnZcTokenRemovingNotional(address, uint256, address, uint256) external returns (bool); 22 | // returns the amount of underlying principal to send 23 | function redeemZcToken(address, uint256, address, uint256) external returns (uint256); 24 | // returns the amount of underlying interest to send 25 | function redeemVaultInterest(address, uint256, address) external returns (uint256); 26 | // returns the cToken address for a given market 27 | function cTokenAddress(address, uint256) external returns (address); 28 | // EVFZE FF EZFVE call this which would then burn zctoken and remove notional 29 | function custodialExit(address, uint256, address, address, uint256) external returns (bool); 30 | // IVFZI && IZFVI call this which would then mint zctoken and add notional 31 | function custodialInitiate(address, uint256, address, address, uint256) external returns (bool); 32 | // IZFZE && EZFZI call this, tranferring zctoken from one party to another 33 | function p2pZcTokenExchange(address, uint256, address, address, uint256) external returns (bool); 34 | // IVFVE && EVFVI call this, removing notional from one party and adding to the other 35 | function p2pVaultExchange(address, uint256, address, address, uint256) external returns (bool); 36 | // IVFZI && IVFVE call this which then transfers notional from msg.sender (taker) to swivel 37 | function transferVaultNotionalFee(address, uint256, address, uint256) external returns (bool); 38 | } 39 | -------------------------------------------------------------------------------- /test/tokens/IZcToken.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/tokens/IErc2612.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Code adapted from OpenZeppelin IERCPermit 3 | pragma solidity >= 0.8.4; 4 | 5 | /** 6 | * @dev Interface of the ERC2612 standard as defined in the EIP. 7 | * 8 | * Adds the {permit} method, which can be used to change one's 9 | * {IERC20-allowance} without having to send a transaction, by signing a 10 | * message. This allows users to spend tokens without having to hold Ether. 11 | * 12 | * See https://eips.ethereum.org/EIPS/eip-2612. 13 | */ 14 | interface IErc2612 { 15 | /** 16 | * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens, 17 | * given `owner`'s signed approval. 18 | * @param o The owner 19 | * @param spender The spender 20 | * @param a The amount 21 | * @param d The deadline 22 | * @param v v portion of the ECDSA 23 | * @param r r portion of the ECDSA 24 | * @param s s portion of the ECDSA 25 | * 26 | * IMPORTANT: The same issues {IERC20-approve} has related to transaction 27 | * ordering also apply here. 28 | * 29 | * Emits an {Approval} event. 30 | * 31 | * Requirements: 32 | * 33 | * - `owner` cannot be the zero address. 34 | * - `spender` cannot be the zero address. 35 | * - `deadline` must be a timestamp in the future. 36 | * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` 37 | * over the EIP712-formatted function arguments. 38 | * - the signature must use ``owner``'s current nonce (see {nonces}). 39 | * 40 | * For more information on the signature format, see the 41 | * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP 42 | * section]. 43 | */ 44 | function permit(address o, address spender, uint256 a, uint256 d, uint8 v, bytes32 r, bytes32 s) external; 45 | 46 | /** 47 | * @dev Returns the current ERC2612 nonce for `owner`. This value must be 48 | * @param o The owner 49 | * 50 | * included whenever a signature is generated for {permit}. 51 | * 52 | * Every successful call to {permit} increases ``owner``'s nonce by one. This 53 | * prevents a signature from being used multiple times. 54 | */ 55 | function nonces(address o) external view returns (uint256); 56 | } 57 | -------------------------------------------------------------------------------- /test/mocks/CErc20.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b50610494806100206000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c8063bd6d894d11610071578063bd6d894d1461018d578063c6bf1552146101ab578063d4e7fdd4146101c7578063d6bcd7aa146101e5578063e7a7b9ce14610203578063e7ba67741461021f576100b4565b806329d9ce3e146100b95780636f307dc3146100d5578063852a12e3146100f35780639ff9f1d414610123578063a0712d681461013f578063ae9d70b01461016f575b600080fd5b6100d360048036038101906100ce9190610352565b61023b565b005b6100dd610245565b6040516100ea91906103c0565b60405180910390f35b61010d60048036038101906101089190610352565b61026f565b60405161011a91906103ea565b60405180910390f35b61013d60048036038101906101389190610352565b610282565b005b61015960048036038101906101549190610352565b61028c565b60405161016691906103ea565b60405180910390f35b61017761029f565b60405161018491906103ea565b60405180910390f35b6101956102a9565b6040516101a291906103ea565b60405180910390f35b6101c560048036038101906101c09190610352565b6102b3565b005b6101cf6102bd565b6040516101dc91906103ea565b60405180910390f35b6101ed6102c3565b6040516101fa91906103ea565b60405180910390f35b61021d60048036038101906102189190610352565b6102c9565b005b61023960048036038101906102349190610431565b6102d3565b005b8060028190555050565b6000600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000816003819055506002549050919050565b8060058190555050565b6000816001819055506000549050919050565b6000600654905090565b6000600554905090565b8060068190555050565b60015481565b60035481565b8060008190555050565b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080fd5b6000819050919050565b61032f8161031c565b811461033a57600080fd5b50565b60008135905061034c81610326565b92915050565b60006020828403121561036857610367610317565b5b60006103768482850161033d565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103aa8261037f565b9050919050565b6103ba8161039f565b82525050565b60006020820190506103d560008301846103b1565b92915050565b6103e48161031c565b82525050565b60006020820190506103ff60008301846103db565b92915050565b61040e8161039f565b811461041957600080fd5b50565b60008135905061042b81610405565b92915050565b60006020828403121561044757610446610317565b5b60006104558482850161041c565b9150509291505056fea264697066735822122095ba17bc1bac2957efb0b1ae576ab4c30b775d581159c1a79d00ac8bc39e2e9964736f6c634300080d0033 -------------------------------------------------------------------------------- /pkg/swiveltesting/approve_underlying_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | "github.com/swivel-finance/gost/test/swivel" 13 | ) 14 | 15 | type approveUnderlyingSuite struct { 16 | suite.Suite 17 | Env *Env 18 | Dep *Dep 19 | Erc20 *mocks.Erc20Session 20 | Swivel *swivel.SwivelSession 21 | } 22 | 23 | func (s *approveUnderlyingSuite) SetupTest() { 24 | var err error 25 | 26 | s.Env = NewEnv(big.NewInt(ONE_ETH)) 27 | s.Dep, err = Deploy(s.Env) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | s.Erc20 = &mocks.Erc20Session{ 34 | Contract: s.Dep.Erc20, 35 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 36 | TransactOpts: bind.TransactOpts{ 37 | From: s.Env.Owner.Opts.From, 38 | Signer: s.Env.Owner.Opts.Signer, 39 | }, 40 | } 41 | 42 | s.Swivel = &swivel.SwivelSession{ 43 | Contract: s.Dep.Swivel, 44 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 45 | TransactOpts: bind.TransactOpts{ 46 | From: s.Env.Owner.Opts.From, 47 | Signer: s.Env.Owner.Opts.Signer, 48 | }, 49 | } 50 | } 51 | 52 | func (s *approveUnderlyingSuite) TestApprove() { 53 | assert := assert.New(s.T()) 54 | 55 | // stub the underlying to return true or the Safe lib will revert 56 | tx, err := s.Erc20.ApproveReturns(true) 57 | assert.NotNil(tx) 58 | assert.Nil(err) 59 | s.Env.Blockchain.Commit() 60 | 61 | uTokens := []common.Address{s.Dep.Erc20Address} 62 | cTokens := []common.Address{s.Dep.CErc20Address} 63 | 64 | tx, err = s.Swivel.ApproveUnderlying(uTokens, cTokens) 65 | 66 | assert.Nil(err) 67 | assert.NotNil(tx) 68 | s.Env.Blockchain.Commit() 69 | 70 | // we just care that the args were passed thru... 71 | amount, err := s.Erc20.ApproveCalled(s.Dep.CErc20Address) 72 | assert.Nil(err) 73 | assert.NotNil(amount) 74 | // it should be for the max sol integer 75 | max := new(big.Int) 76 | max = max.Exp(big.NewInt(2), big.NewInt(256), nil).Sub(max, big.NewInt(1)) 77 | assert.Equal(amount, max) 78 | } 79 | 80 | func TestApproveUnderlyingSuite(t *test.T) { 81 | suite.Run(t, &approveUnderlyingSuite{}) 82 | } 83 | -------------------------------------------------------------------------------- /internal/helpers/helpers.go: -------------------------------------------------------------------------------- 1 | // this file can be split up into foo_helper, bar_helpers etc to prevent ye olde monolith 2 | 3 | package helpers 4 | 5 | import ( 6 | "crypto/ecdsa" 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/crypto" 13 | "github.com/swivel-finance/gost/test/fakes" 14 | ) 15 | 16 | func NewAuth(c *big.Int) (*ecdsa.PrivateKey, *bind.TransactOpts) { 17 | pk, _ := crypto.GenerateKey() 18 | opts, _ := bind.NewKeyedTransactorWithChainID(pk, c) 19 | return pk, opts 20 | } 21 | 22 | // GenBytes32 is a convenience function for some tests where a "GetHash" method is not available. 23 | // Returns a type compatible with bytes32, given a string 32 chars or less. 24 | func GenBytes32(s string) [32]byte { 25 | bytes := [32]byte{} 26 | copy(bytes[:], []byte(s)) 27 | return bytes 28 | } 29 | 30 | // NewCallOpts is a function which allows us to more succinctly place Call options 31 | func NewCallOpts(a common.Address) *bind.CallOpts { 32 | return &bind.CallOpts{ 33 | // TODO: Should this be a settable argument? 34 | Pending: false, 35 | From: a, 36 | } 37 | } 38 | 39 | // NewTxOpts is a function which allows us to more succintly get a hydrated TransactOpts object 40 | func NewTransactOpts(a *bind.TransactOpts, v *big.Int, p *big.Int, l uint64) *bind.TransactOpts { 41 | return &bind.TransactOpts{ 42 | From: a.From, 43 | Signer: a.Signer, 44 | Value: v, 45 | GasPrice: p, 46 | GasLimit: l, 47 | } 48 | } 49 | 50 | // Commafy will take a big integer and return a string with commas so that logging 51 | // big integers is human readable 52 | func Commafy(n *big.Int) string { 53 | in := fmt.Sprintf("%d", n) 54 | out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3) 55 | 56 | for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 { 57 | out[j] = in[i] 58 | if i == 0 { 59 | return string(out) 60 | } 61 | if k++; k == 3 { 62 | j, k = j-1, 0 63 | out[j] = ',' 64 | } 65 | } 66 | } 67 | 68 | // NewHashOrder will take args to hydrate a Hash.Order and return it 69 | func NewHashOrder( 70 | k [32]byte, 71 | maker common.Address, 72 | u common.Address, 73 | v bool, 74 | exit bool, 75 | principal int64, 76 | premium int64, 77 | maturity int64, 78 | expiry int64, 79 | ) fakes.HashOrder { 80 | return fakes.HashOrder{ 81 | Key: k, 82 | Maker: maker, 83 | Underlying: u, 84 | Vault: v, 85 | Exit: exit, 86 | Principal: big.NewInt(principal), 87 | Premium: big.NewInt(premium), 88 | Maturity: big.NewInt(maturity), 89 | Expiry: big.NewInt(expiry), 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkg/swiveltesting/transfer_admin_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/swivel" 11 | ) 12 | 13 | type transferAdminSuite struct { 14 | suite.Suite 15 | Env *Env 16 | Dep *Dep 17 | SwivelOwner *swivel.SwivelSession // *Session objects are created by the go bindings 18 | SwivelUser *swivel.SwivelSession 19 | } 20 | 21 | func (s *transferAdminSuite) SetupSuite() { 22 | var err error 23 | 24 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 25 | s.Dep, err = Deploy(s.Env) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // binding owner to both, kind of why it exists - but could be any of the env wallets 32 | s.SwivelOwner = &swivel.SwivelSession{ 33 | Contract: s.Dep.Swivel, 34 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 35 | TransactOpts: bind.TransactOpts{ 36 | From: s.Env.Owner.Opts.From, 37 | Signer: s.Env.Owner.Opts.Signer, 38 | }, 39 | } 40 | 41 | // using a session bound to each so we can switch the admin back... 42 | s.SwivelUser = &swivel.SwivelSession{ 43 | Contract: s.Dep.Swivel, 44 | CallOpts: bind.CallOpts{From: s.Env.User1.Opts.From, Pending: false}, 45 | TransactOpts: bind.TransactOpts{ 46 | From: s.Env.User1.Opts.From, 47 | Signer: s.Env.User1.Opts.Signer, 48 | }, 49 | } 50 | } 51 | 52 | func (s *transferAdminSuite) TestTransferAdmin() { 53 | assert := assert.New(s.T()) 54 | 55 | // check the current address, it should be owner 56 | // NOTE don't confuse the 2 bound sessions for the contract, admin is the same in both... 57 | addr, err := s.SwivelUser.Admin() 58 | assert.Nil(err) 59 | assert.Equal(addr, s.Env.Owner.Opts.From) 60 | 61 | // transfer it to User1, must be done from owner (current admin) 62 | tx, err := s.SwivelOwner.TransferAdmin(s.Env.User1.Opts.From) 63 | assert.Nil(err) 64 | assert.NotNil(tx) 65 | 66 | s.Env.Blockchain.Commit() 67 | 68 | // admin address should have changed 69 | addr, err = s.SwivelOwner.Admin() 70 | assert.Nil(err) 71 | assert.Equal(addr, s.Env.User1.Opts.From) 72 | 73 | // change it back so everything doesn't fail... (must be done from user, as that's the admin now) 74 | tx, err = s.SwivelUser.TransferAdmin(s.Env.Owner.Opts.From) 75 | assert.Nil(err) 76 | assert.NotNil(tx) 77 | 78 | s.Env.Blockchain.Commit() 79 | 80 | // admin address is owner again... 81 | addr, err = s.SwivelUser.Admin() 82 | assert.Nil(err) 83 | assert.Equal(addr, s.Env.Owner.Opts.From) 84 | } 85 | 86 | func TestTransferAdminSuite(t *test.T) { 87 | suite.Run(t, &transferAdminSuite{}) 88 | } 89 | -------------------------------------------------------------------------------- /pkg/testing/dep.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/swivel-finance/gost/test/fakes" 8 | "github.com/swivel-finance/gost/test/mocks" 9 | "github.com/swivel-finance/gost/test/tokens" 10 | ) 11 | 12 | type Dep struct { 13 | SigFakeAddress common.Address 14 | SigFake *fakes.SigFake // fake sig lib test contract 15 | HashFakeAddress common.Address 16 | HashFake *fakes.HashFake // fake hash lib test contract 17 | Erc20Address common.Address 18 | Erc20 *mocks.Erc20 19 | CErc20Address common.Address 20 | CErc20 *mocks.CErc20 21 | FErc20Address common.Address 22 | FErc20 *mocks.FErc20 23 | ZcTokenAddress common.Address 24 | ZcToken *tokens.ZcToken 25 | } 26 | 27 | func Deploy(e *Env) (*Dep, error) { 28 | // deploying the lib testing contract "fakes" 29 | // NOTE these _could_ be moved into their own package as they are not needed 30 | // for swivel to operate. TODO 31 | sigAddress, _, sigContract, sigErr := fakes.DeploySigFake(e.Owner.Opts, e.Blockchain) 32 | 33 | if sigErr != nil { 34 | return nil, sigErr 35 | } 36 | 37 | e.Blockchain.Commit() 38 | 39 | hashAddress, _, hashContract, hashErr := fakes.DeployHashFake(e.Owner.Opts, e.Blockchain) 40 | 41 | if hashErr != nil { 42 | return nil, hashErr 43 | } 44 | 45 | e.Blockchain.Commit() 46 | 47 | // deploy the two mock tokens. 48 | ercAddress, _, ercContract, ercErr := mocks.DeployErc20(e.Owner.Opts, e.Blockchain) 49 | 50 | if ercErr != nil { 51 | return nil, ercErr 52 | } 53 | 54 | e.Blockchain.Commit() 55 | 56 | cercAddress, _, cercContract, cercErr := mocks.DeployCErc20(e.Owner.Opts, e.Blockchain) 57 | 58 | if cercErr != nil { 59 | return nil, cercErr 60 | } 61 | 62 | e.Blockchain.Commit() 63 | 64 | fercAddress, _, fercContract, fercErr := mocks.DeployFErc20(e.Owner.Opts, e.Blockchain) 65 | 66 | if fercErr != nil { 67 | return nil, fercErr 68 | } 69 | 70 | e.Blockchain.Commit() 71 | 72 | // deploy a real ZcToken in order to test the maturity requirement 73 | zctAddress, _, zctContract, zctErr := tokens.DeployZcToken(e.Owner.Opts, e.Blockchain, ercAddress, big.NewInt(MATURITY), "YoloToken", "YT", uint8(18)) 74 | 75 | if zctErr != nil { 76 | return nil, zctErr 77 | } 78 | 79 | e.Blockchain.Commit() 80 | 81 | return &Dep{ 82 | SigFakeAddress: sigAddress, 83 | SigFake: sigContract, 84 | HashFakeAddress: hashAddress, 85 | HashFake: hashContract, 86 | Erc20Address: ercAddress, 87 | Erc20: ercContract, 88 | CErc20Address: cercAddress, 89 | CErc20: cercContract, 90 | FErc20Address: fercAddress, 91 | FErc20: fercContract, 92 | ZcTokenAddress: zctAddress, 93 | ZcToken: zctContract, 94 | }, nil 95 | } 96 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/balances_of_test.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/mocks" 11 | "github.com/swivel-finance/gost/test/vaulttracker" 12 | ) 13 | 14 | type balancesOfSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | CErc20 *mocks.CErc20Session 19 | VaultTracker *vaulttracker.VaultTrackerSession // *Session objects are created by the go bindings 20 | } 21 | 22 | func (s *balancesOfSuite) SetupTest() { 23 | var err error 24 | 25 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 26 | s.Dep, err = Deploy(s.Env) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 32 | if err != nil { 33 | panic(err) 34 | } 35 | s.Env.Blockchain.Commit() 36 | 37 | s.CErc20 = &mocks.CErc20Session{ 38 | Contract: s.Dep.CErc20, 39 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 40 | TransactOpts: bind.TransactOpts{ 41 | From: s.Env.Owner.Opts.From, 42 | Signer: s.Env.Owner.Opts.Signer, 43 | }, 44 | } 45 | 46 | // binding owner to both, kind of why it exists - but could be any of the env wallets 47 | s.VaultTracker = &vaulttracker.VaultTrackerSession{ 48 | Contract: s.Dep.VaultTracker, 49 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 50 | TransactOpts: bind.TransactOpts{ 51 | From: s.Env.Owner.Opts.From, 52 | Signer: s.Env.Owner.Opts.Signer, 53 | }, 54 | } 55 | } 56 | 57 | func (s *balancesOfSuite) TestBalancesOf() { 58 | assert := assertions.New(s.T()) 59 | 60 | rate1 := big.NewInt(123456789) 61 | tx, err := s.CErc20.ExchangeRateCurrentReturns(rate1) 62 | assert.Nil(err) 63 | assert.NotNil(tx) 64 | s.Env.Blockchain.Commit() 65 | 66 | // balance of the Owner 67 | notional, redeemable, err := s.VaultTracker.BalancesOf(s.Env.Owner.Opts.From) 68 | assert.Nil(err) 69 | assert.Equal(notional.Cmp(redeemable), 0) 70 | assert.Equal(redeemable.Cmp(ZERO), 0) 71 | 72 | // call AddNotional for Owner with no vault 73 | caller := s.Env.Owner.Opts.From 74 | amount := big.NewInt(10000000) 75 | tx, err = s.VaultTracker.AddNotional(caller, amount) 76 | assert.Nil(err) 77 | assert.NotNil(tx) 78 | 79 | s.Env.Blockchain.Commit() 80 | 81 | // balance of the Owner 82 | notional, redeemable, err = s.VaultTracker.BalancesOf(s.Env.Owner.Opts.From) 83 | assert.Nil(err) 84 | assert.Equal(amount, notional) 85 | assert.Equal(redeemable.Cmp(ZERO), 0) 86 | } 87 | 88 | func TestBalancesOfSuite(t *test.T) { 89 | suite.Run(t, &balancesOfSuite{}) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/transfer_admin_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/marketplace" 11 | ) 12 | 13 | type transferAdminSuite struct { 14 | suite.Suite 15 | Env *Env 16 | Dep *Dep 17 | MarketPlaceOwner *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 18 | MarketPlaceUser *marketplace.MarketPlaceSession 19 | } 20 | 21 | func (s *transferAdminSuite) SetupSuite() { 22 | var err error 23 | 24 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 25 | s.Dep, err = Deploy(s.Env) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | s.MarketPlaceOwner = &marketplace.MarketPlaceSession{ 32 | Contract: s.Dep.MarketPlace, 33 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 34 | TransactOpts: bind.TransactOpts{ 35 | From: s.Env.Owner.Opts.From, 36 | Signer: s.Env.Owner.Opts.Signer, 37 | }, 38 | } 39 | 40 | // we'll bind a second instance to user so we cann change it back without having to pass the opts directly... 41 | s.MarketPlaceUser = &marketplace.MarketPlaceSession{ 42 | Contract: s.Dep.MarketPlace, 43 | CallOpts: bind.CallOpts{From: s.Env.User1.Opts.From, Pending: false}, 44 | TransactOpts: bind.TransactOpts{ 45 | From: s.Env.User1.Opts.From, 46 | Signer: s.Env.User1.Opts.Signer, 47 | }, 48 | } 49 | } 50 | 51 | func (s *transferAdminSuite) TestTransferAdmin() { 52 | assert := assert.New(s.T()) 53 | 54 | // check the current address, it should be owner 55 | // NOTE don't confuse the 2 bound sessions for the contract, admin is the same in both... 56 | addr, err := s.MarketPlaceUser.Admin() 57 | assert.Nil(err) 58 | assert.Equal(addr, s.Env.Owner.Opts.From) 59 | 60 | // transfer it to User1, must be done from owner (current admin) 61 | tx, err := s.MarketPlaceOwner.TransferAdmin(s.Env.User1.Opts.From) 62 | assert.Nil(err) 63 | assert.NotNil(tx) 64 | 65 | s.Env.Blockchain.Commit() 66 | 67 | // admin address should have changed 68 | addr, err = s.MarketPlaceOwner.Admin() 69 | assert.Nil(err) 70 | assert.Equal(addr, s.Env.User1.Opts.From) 71 | 72 | // change it back so everything doesn't fail... (must be done from user, as that's the admin now) 73 | tx, err = s.MarketPlaceUser.TransferAdmin(s.Env.Owner.Opts.From) 74 | assert.Nil(err) 75 | assert.NotNil(tx) 76 | 77 | s.Env.Blockchain.Commit() 78 | 79 | // admin address is owner again... 80 | addr, err = s.MarketPlaceUser.Admin() 81 | assert.Nil(err) 82 | assert.Equal(addr, s.Env.Owner.Opts.From) 83 | } 84 | 85 | func TestTransferAdminSuite(t *test.T) { 86 | suite.Run(t, &transferAdminSuite{}) 87 | } 88 | -------------------------------------------------------------------------------- /test/tokens/Hash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | /** 6 | @notice Encapsulation of the logic to produce EIP712 hashed domain and messages. 7 | Also to produce / verify hashed and signed Permits. 8 | */ 9 | 10 | library Hash { 11 | // EIP712 Domain Separator typeHash 12 | // keccak256(abi.encodePacked( 13 | // 'EIP712Domain(', 14 | // 'string name,', 15 | // 'string version,', 16 | // 'uint256 chainId,', 17 | // 'address verifyingContract', 18 | // ')' 19 | // )); 20 | bytes32 constant internal DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; 21 | 22 | // EIP2612 typeHash of a Permit 23 | // keccak256(abi.encodePacked( 24 | // 'Permit(', 25 | // 'address owner,', 26 | // 'address spender,', 27 | // 'uint256 value,', 28 | // 'uint256 nonce,', 29 | // 'uint256 deadline,', 30 | // ')' 31 | // )); 32 | bytes32 constant internal PERMIT_TYPEHASH = 0x80772249b4aef1688b30651778f4249b05cb73b517d98482439b9d8999b30602; 33 | 34 | /// @param n EIP712 domain name 35 | /// @param version EIP712 semantic version string 36 | /// @param i Chain ID 37 | /// @param verifier address of the verifying contract 38 | function domain(string memory n, string memory version, uint256 i, address verifier) internal pure returns (bytes32) { 39 | bytes32 hash; 40 | 41 | assembly { 42 | let nameHash := keccak256(add(n, 32), mload(n)) 43 | let versionHash := keccak256(add(version, 32), mload(version)) 44 | let pointer := mload(64) 45 | mstore(pointer, DOMAIN_TYPEHASH) 46 | mstore(add(pointer, 32), nameHash) 47 | mstore(add(pointer, 64), versionHash) 48 | mstore(add(pointer, 96), i) 49 | mstore(add(pointer, 128), verifier) 50 | hash := keccak256(pointer, 160) 51 | } 52 | 53 | return hash; 54 | } 55 | 56 | /// @param d Type hash of the domain separator (see Hash.domain) 57 | /// @param h EIP712 hash struct (Permit for example) 58 | function message(bytes32 d, bytes32 h) internal pure returns (bytes32) { 59 | bytes32 hash; 60 | 61 | assembly { 62 | let pointer := mload(64) 63 | mstore(pointer, 0x1901000000000000000000000000000000000000000000000000000000000000) 64 | mstore(add(pointer, 2), d) 65 | mstore(add(pointer, 34), h) 66 | hash := keccak256(pointer, 66) 67 | } 68 | 69 | return hash; 70 | } 71 | 72 | /// @param o Address of the owner 73 | /// @param s Address of the spender 74 | /// @param a Amount to be approved 75 | /// @param n Current nonce 76 | /// @param d Deadline at which the permission is no longer valid 77 | function permit(address o, address s, uint256 a, uint256 n, uint256 d) internal pure returns (bytes32) { 78 | return keccak256(abi.encode(PERMIT_TYPEHASH, o, s, a, n, d)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/vaulttracker/VaultTracker.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"address","name":"c","type":"address"},{"internalType":"address","name":"s","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"addNotional","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"}],"name":"balancesOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cTokenAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"c","type":"uint256"}],"name":"matureVault","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maturity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maturityRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"}],"name":"redeemInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"removeNotional","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swivel","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferNotionalFee","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferNotionalFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"vaults","outputs":[{"internalType":"uint256","name":"notional","type":"uint256"},{"internalType":"uint256","name":"redeemable","type":"uint256"},{"internalType":"uint256","name":"exchangeRate","type":"uint256"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /pkg/swiveltesting/dep.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/swivel-finance/gost/test/fakes" 8 | "github.com/swivel-finance/gost/test/mocks" 9 | "github.com/swivel-finance/gost/test/swivel" 10 | ) 11 | 12 | // TODO mock for marketplace... 13 | type Dep struct { 14 | SigFakeAddress common.Address 15 | SigFake *fakes.SigFake // fake sig lib test contract 16 | HashFakeAddress common.Address 17 | HashFake *fakes.HashFake // fake hash lib test contract 18 | Erc20Address common.Address 19 | Erc20 *mocks.Erc20 // mock erc20 20 | CErc20Address common.Address 21 | CErc20 *mocks.CErc20 // mock erc20 22 | MarketPlaceAddress common.Address 23 | MarketPlace *mocks.MarketPlace // mock marketplace 24 | Maturity *big.Int 25 | SwivelAddress common.Address 26 | Swivel *swivel.Swivel 27 | } 28 | 29 | func Deploy(e *Env) (*Dep, error) { 30 | maturity := big.NewInt(MATURITY) 31 | // deploy the fakes so we can access the libs from tests 32 | sigAddress, _, sigContract, sigErr := fakes.DeploySigFake(e.Owner.Opts, e.Blockchain) 33 | 34 | if sigErr != nil { 35 | return nil, sigErr 36 | } 37 | 38 | e.Blockchain.Commit() 39 | 40 | hashAddress, _, hashContract, hashErr := fakes.DeployHashFake(e.Owner.Opts, e.Blockchain) 41 | 42 | if hashErr != nil { 43 | return nil, hashErr 44 | } 45 | 46 | e.Blockchain.Commit() 47 | 48 | // deploy the two mock tokens. 49 | ercAddress, _, ercContract, ercErr := mocks.DeployErc20(e.Owner.Opts, e.Blockchain) 50 | 51 | if ercErr != nil { 52 | return nil, ercErr 53 | } 54 | 55 | e.Blockchain.Commit() 56 | 57 | cercAddress, _, cercContract, cercErr := mocks.DeployCErc20(e.Owner.Opts, e.Blockchain) 58 | 59 | if cercErr != nil { 60 | return nil, cercErr 61 | } 62 | 63 | e.Blockchain.Commit() 64 | 65 | marketAddress, _, marketContract, marketErr := mocks.DeployMarketPlace(e.Owner.Opts, e.Blockchain) 66 | 67 | if marketErr != nil { 68 | return nil, marketErr 69 | } 70 | 71 | e.Blockchain.Commit() 72 | 73 | // deploy swivel contract, (using a swivel staging deploy as verifier here) 74 | swivelAddress, _, swivelContract, swivelErr := swivel.DeploySwivel(e.Owner.Opts, e.Blockchain, marketAddress, common.HexToAddress("0x3a09584FF42CDFe27Fe72Da0533bba24E9C28AaD")) 75 | 76 | if swivelErr != nil { 77 | return nil, swivelErr 78 | } 79 | 80 | e.Blockchain.Commit() 81 | 82 | return &Dep{ 83 | SigFakeAddress: sigAddress, 84 | SigFake: sigContract, 85 | HashFakeAddress: hashAddress, 86 | HashFake: hashContract, 87 | Erc20Address: ercAddress, 88 | Erc20: ercContract, 89 | CErc20Address: cercAddress, 90 | CErc20: cercContract, 91 | MarketPlaceAddress: marketAddress, 92 | MarketPlace: marketContract, 93 | Maturity: maturity, 94 | SwivelAddress: swivelAddress, 95 | Swivel: swivelContract, 96 | }, nil 97 | } 98 | -------------------------------------------------------------------------------- /test/tokens/PErc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"string","name":"n","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"uint8","name":"d","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/marketplacetesting/get_ctoken_address_address_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/marketplace" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | ) 13 | 14 | type cTokenAddrSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 21 | } 22 | 23 | func (s *cTokenAddrSuite) SetupSuite() { 24 | var err error 25 | 26 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 27 | s.Dep, err = Deploy(s.Env) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | s.Erc20 = &mocks.Erc20Session{ 34 | Contract: s.Dep.Erc20, 35 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 36 | TransactOpts: bind.TransactOpts{ 37 | From: s.Env.Owner.Opts.From, 38 | Signer: s.Env.Owner.Opts.Signer, 39 | }, 40 | } 41 | 42 | s.CErc20 = &mocks.CErc20Session{ 43 | Contract: s.Dep.CErc20, 44 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 45 | TransactOpts: bind.TransactOpts{ 46 | From: s.Env.Owner.Opts.From, 47 | Signer: s.Env.Owner.Opts.Signer, 48 | }, 49 | } 50 | 51 | s.MarketPlace = &marketplace.MarketPlaceSession{ 52 | Contract: s.Dep.MarketPlace, 53 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 54 | TransactOpts: bind.TransactOpts{ 55 | From: s.Env.Owner.Opts.From, 56 | Signer: s.Env.Owner.Opts.Signer, 57 | }, 58 | } 59 | } 60 | 61 | func (s *cTokenAddrSuite) TestSetCTokenAddr() { 62 | assert := assertions.New(s.T()) 63 | // the swivel address must be set 64 | _, err := s.MarketPlace.SetSwivelAddress(s.Dep.SwivelAddress) 65 | assert.Nil(err) 66 | s.Env.Blockchain.Commit() 67 | // addresses can be BS in this test... 68 | underlying := s.Dep.Erc20Address 69 | maturity := big.NewInt(123456789) 70 | cTokenAddr := s.Dep.CErc20Address 71 | 72 | tx, err := s.Erc20.DecimalsReturns(uint8(18)) 73 | assert.Nil(err) 74 | assert.NotNil(tx) 75 | s.Env.Blockchain.Commit() 76 | 77 | tx, err = s.CErc20.UnderlyingReturns(underlying) 78 | assert.Nil(err) 79 | assert.NotNil(tx) 80 | s.Env.Blockchain.Commit() 81 | 82 | tx, err = s.MarketPlace.CreateMarket( 83 | maturity, 84 | cTokenAddr, 85 | "awesome market", 86 | "AM", 87 | ) 88 | 89 | assert.Nil(err) 90 | assert.NotNil(tx) 91 | s.Env.Blockchain.Commit() 92 | 93 | cTokenAddrC, err := s.MarketPlace.CTokenAddress(underlying, maturity) 94 | assert.Nil(err) 95 | assert.Equal(cTokenAddr, cTokenAddrC) 96 | } 97 | 98 | func TestCTokenAddrSuite(t *test.T) { 99 | suite.Run(t, &cTokenAddrSuite{}) 100 | } 101 | -------------------------------------------------------------------------------- /test/marketplace/ZcToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | ZcToken is a mock which records arguments passed to its methods as well as 5 | provides setters allowing us to dictate method return values 6 | */ 7 | 8 | pragma solidity >= 0.8.4; 9 | 10 | contract ZcToken { 11 | // a struct to hold the arguments passed to transferFrom 12 | struct TransferFromArgs { 13 | address to; 14 | uint256 amount; 15 | } 16 | 17 | // mapping of arguments sent to burn. key is the passed in address. 18 | mapping (address => uint256) public burnCalled; 19 | // mapping of arguments sent to mint. key is the passed in address. 20 | mapping (address => uint256) public mintCalled; 21 | // mapping of arguments sent to transferFrom. key is passed from address. 22 | mapping (address => TransferFromArgs) public transferFromCalled; 23 | 24 | string public name; 25 | string public symbol; 26 | uint8 public decimals; 27 | address private underlyingReturn; 28 | uint256 private maturityReturn; 29 | // a boolean flag which allows us to dictate the return of burn(). 30 | bool private burnReturn; 31 | // a boolean flag which allows us to dictate the return of mint(). 32 | bool private mintReturn; 33 | // a boolean flag which allows us to dictate the return of transferFrom(). 34 | bool private transferFromReturn; 35 | 36 | /// @param u Underlying 37 | /// @param m Maturity 38 | /// @param n Name 39 | /// @param s Symbol 40 | /// @param d Decimals 41 | constructor(address u, uint256 m, string memory n, string memory s, uint8 d) { 42 | // we can set the privates in the constructor as well... 43 | underlyingReturn = u; 44 | maturityReturn = m; 45 | 46 | name = n; 47 | symbol = s; 48 | decimals = d; 49 | } 50 | 51 | function burnReturns(bool b) public { 52 | burnReturn = b; 53 | } 54 | 55 | function burn(address f, uint256 a) public returns(bool) { 56 | burnCalled[f] = a; 57 | return burnReturn; 58 | } 59 | 60 | function mintReturns(bool b) public { 61 | mintReturn = b; 62 | } 63 | 64 | function mint(address f, uint256 a) public returns(bool) { 65 | mintCalled[f] = a; 66 | return mintReturn; 67 | } 68 | 69 | function underlyingReturns(address u) public { 70 | underlyingReturn = u; 71 | } 72 | 73 | // override what would be the autogenerated getter... 74 | function underlying() public view returns (address) { 75 | return underlyingReturn; 76 | } 77 | 78 | function maturityReturns(uint256 n) public { 79 | maturityReturn = n; 80 | } 81 | 82 | // override what would be the autogenerated getter... 83 | function maturity() public view returns (uint256) { 84 | return maturityReturn; 85 | } 86 | 87 | function transferFrom(address f, address t, uint256 a) public returns (bool) { 88 | TransferFromArgs memory args; 89 | args.to = t; 90 | args.amount = a; 91 | transferFromCalled[f] = args; 92 | return transferFromReturn; 93 | } 94 | 95 | function transferFromReturns(bool b) public { 96 | transferFromReturn = b; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/tokens/IPErc20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | /** 6 | * @dev Interface of the ERC20 standard as defined in the EIP. 7 | */ 8 | interface IPErc20 { 9 | /** 10 | * @dev Returns the amount of tokens owned by `account`. 11 | * @param a Adress to fetch balance of 12 | */ 13 | function balanceOf(address a) external view returns (uint256); 14 | 15 | /** 16 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 17 | * @param r The recipient 18 | * @param a The amount transferred 19 | * 20 | * Emits a {Transfer} event. 21 | */ 22 | function transfer(address r, uint256 a) external returns (bool); 23 | 24 | /** 25 | * @dev Returns the remaining number of tokens that `spender` will be 26 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 27 | * zero by default. 28 | * @param o The owner 29 | * @param s The spender 30 | * 31 | * This value changes when {approve} or {transferFrom} are called. 32 | */ 33 | function allowance(address o, address s) external view returns (uint256); 34 | 35 | /** 36 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 37 | * 38 | * Returns a boolean value indicating whether the operation succeeded. 39 | * 40 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 41 | * that someone may use both the old and the new allowance by unfortunate 42 | * transaction ordering. One possible solution to mitigate this race 43 | * condition is to first reduce the spender's allowance to 0 and set the 44 | * desired value afterwards: 45 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 46 | * @param s The spender 47 | * @param a The amount to approve 48 | * 49 | * Emits an {Approval} event. 50 | */ 51 | function approve(address s, uint256 a) external returns (bool); 52 | 53 | /** 54 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 55 | * allowance mechanism. `amount` is then deducted from the caller's 56 | * allowance. 57 | * @param s The sender 58 | * @param r The recipient 59 | * @param a The amount to transfer 60 | * 61 | * Returns a boolean value indicating whether the operation succeeded. 62 | * 63 | * Emits a {Transfer} event. 64 | */ 65 | function transferFrom(address s, address r, uint256 a) external returns (bool); 66 | 67 | /** 68 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 69 | * another (`to`). 70 | * 71 | * Note that `value` may be zero. 72 | */ 73 | event Transfer(address indexed from, address indexed to, uint256 value); 74 | 75 | /** 76 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 77 | * a call to {approve}. `value` is the new allowance. 78 | */ 79 | event Approval(address indexed owner, address indexed spender, uint256 value); 80 | } 81 | -------------------------------------------------------------------------------- /test/marketplace/ZcToken.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"string","name":"n","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"uint8","name":"d","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"burnCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"burnReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maturity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"maturityReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"mintCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"mintReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"transferFromCalled","outputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"transferFromReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"underlying","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"}],"name":"underlyingReturns","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/testing/zc_token_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | "time" 7 | 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | "github.com/swivel-finance/gost/test/tokens" 13 | ) 14 | 15 | type zcTokenSuite struct { 16 | suite.Suite 17 | Env *Env 18 | Dep *Dep 19 | Erc20 *mocks.Erc20Session // *Session objects are created by the go bindings 20 | ZcToken *tokens.ZcTokenSession 21 | } 22 | 23 | func (s *zcTokenSuite) SetupSuite() { 24 | var err error 25 | 26 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 27 | s.Dep, err = Deploy(s.Env) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | s.Erc20 = &mocks.Erc20Session{ 34 | Contract: s.Dep.Erc20, 35 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 36 | TransactOpts: bind.TransactOpts{ 37 | From: s.Env.Owner.Opts.From, 38 | Signer: s.Env.Owner.Opts.Signer, 39 | }, 40 | } 41 | 42 | s.ZcToken = &tokens.ZcTokenSession{ 43 | Contract: s.Dep.ZcToken, 44 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 45 | TransactOpts: bind.TransactOpts{ 46 | From: s.Env.Owner.Opts.From, 47 | Signer: s.Env.Owner.Opts.Signer, 48 | }, 49 | } 50 | } 51 | 52 | func (s *zcTokenSuite) TestName() { 53 | assert := assert.New(s.T()) 54 | 55 | name, err := s.ZcToken.Name() 56 | assert.Nil(err) 57 | assert.Equal("YoloToken", name) 58 | } 59 | 60 | func (s *zcTokenSuite) TestSymbol() { 61 | assert := assert.New(s.T()) 62 | 63 | sym, err := s.ZcToken.Symbol() 64 | assert.Nil(err) 65 | assert.Equal("YT", sym) 66 | } 67 | 68 | func (s *zcTokenSuite) TestDecimals() { 69 | assert := assert.New(s.T()) 70 | 71 | dec, err := s.ZcToken.Decimals() 72 | assert.Nil(err) 73 | assert.Equal(uint8(18), dec) 74 | } 75 | 76 | func (s *zcTokenSuite) TestMint() { 77 | assert := assert.New(s.T()) 78 | 79 | // get initial totalSupply and reciever balance as both should increase 80 | supply, _ := s.ZcToken.TotalSupply() 81 | bal, _ := s.ZcToken.BalanceOf(s.Env.User1.Opts.From) 82 | 83 | tx, err := s.ZcToken.Mint(s.Env.User1.Opts.From, big.NewInt(1000000)) 84 | assert.Nil(err) 85 | assert.NotNil(tx) 86 | 87 | s.Env.Blockchain.Commit() 88 | 89 | // get the balances again... 90 | newSupply, _ := s.ZcToken.TotalSupply() 91 | newBal, _ := s.ZcToken.BalanceOf(s.Env.User1.Opts.From) 92 | 93 | // both sohuld be greater than the previous balances... 94 | assert.Equal(newSupply.Cmp(supply), 1) 95 | assert.Equal(newBal.Cmp(bal), 1) 96 | } 97 | 98 | func (s *zcTokenSuite) TestMintFailsWhenMature() { 99 | assert := assert.New(s.T()) 100 | 101 | // move past the maturity in order to create the fail condition 102 | err := s.Env.Blockchain.AdjustTime(MATURITY * time.Second) 103 | assert.Nil(err) 104 | s.Env.Blockchain.Commit() 105 | 106 | tx, err := s.ZcToken.Mint(s.Env.User1.Opts.From, big.NewInt(1000000)) 107 | // should have an err... 108 | assert.Nil(tx) 109 | assert.NotNil(err) 110 | 111 | // error should be that the maturity has been exceeded 112 | assert.Regexp("maturity reached", err.Error()) 113 | } 114 | 115 | func TestZcTokenSuite(t *test.T) { 116 | suite.Run(t, &zcTokenSuite{}) 117 | } 118 | -------------------------------------------------------------------------------- /pkg/testing/hash_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | "github.com/swivel-finance/gost/internal/helpers" 12 | "github.com/swivel-finance/gost/test/fakes" 13 | ) 14 | 15 | type hashTestSuite struct { 16 | suite.Suite 17 | Env *Env 18 | Dep *Dep 19 | Separator [32]byte // keep a ref to prevent re calculating 20 | } 21 | 22 | // helper to return a hydrated order. TODO move to a helper... 23 | func order(m common.Address, f bool) fakes.HashOrder { // abigen defined 24 | // NOTE: none of the actual numbers used matter here for the purpose of this test. 25 | return fakes.HashOrder{ 26 | Key: helpers.GenBytes32("abc123"), 27 | Maker: m, 28 | Underlying: common.HexToAddress("0xbcd234"), 29 | Vault: f, 30 | Exit: f, 31 | Principal: big.NewInt(1000), 32 | Premium: big.NewInt(100), 33 | Maturity: big.NewInt(123456), 34 | Expiry: big.NewInt(123456789), 35 | } 36 | } 37 | 38 | // SetupSuite serves as a 'beforeAll', hydrating both the Env and Dep objects 39 | func (s *hashTestSuite) SetupSuite() { 40 | // declared because we can't use := here (s.Dep already defined) 41 | var err error 42 | 43 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 44 | s.Dep, err = Deploy(s.Env) 45 | 46 | if err != nil { 47 | panic(err) 48 | } 49 | } 50 | 51 | // NOTE: present just to calculate the hashed values stored in the contracts 52 | func (s *hashTestSuite) TestTypeHashes() { 53 | domainTypehash, _ := s.Dep.HashFake.DomainTypeHash(nil) 54 | orderTypehash, _ := s.Dep.HashFake.OrderTypeHash(nil) 55 | permitTypehash, _ := s.Dep.HashFake.PermitTypeHash(nil) 56 | s.T().Logf("Domain Typehash: %v", hexutil.Encode(domainTypehash[:])) 57 | s.T().Logf("Order Typehash: %v", hexutil.Encode(orderTypehash[:])) 58 | s.T().Logf("Permit Typehash: %v", hexutil.Encode(permitTypehash[:])) 59 | } 60 | 61 | func (s *hashTestSuite) TestDomain() { 62 | var err error 63 | 64 | assert := assert.New(s.T()) 65 | 66 | s.Separator, err = s.Dep.HashFake.DomainTest( 67 | nil, 68 | "Swivel Finance", 69 | "2.0.0", 70 | big.NewInt(5), 71 | common.HexToAddress("0x6a6BeC42A5Dd6F2766F806F91Ad12034F43b6361"), 72 | ) 73 | 74 | assert.Nil(err) 75 | assert.NotNil(s.Separator) 76 | assert.Equal(len(s.Separator), 32) 77 | // s.T().Log(hexutil.Encode(s.Separator[:])) 78 | } 79 | 80 | func (s *hashTestSuite) TestOrder() { 81 | assert := assert.New(s.T()) 82 | 83 | order := order(s.Env.Owner.Opts.From, false) 84 | 85 | hash, err := s.Dep.HashFake.OrderTest(nil, order) 86 | 87 | assert.Nil(err) 88 | assert.NotNil(hash) 89 | assert.Equal(len(hash), 32) 90 | } 91 | 92 | func (s *hashTestSuite) TestMessage() { 93 | assert := assert.New(s.T()) 94 | // get a hashed order 95 | 96 | order := order(s.Env.Owner.Opts.From, false) 97 | orderHash, _ := s.Dep.HashFake.OrderTest(nil, order) 98 | 99 | messageHash, err := s.Dep.HashFake.MessageTest(nil, s.Separator, orderHash) 100 | 101 | assert.Nil(err) 102 | assert.NotNil(messageHash) 103 | assert.Equal(len(messageHash), 32) 104 | } 105 | 106 | func TestHashSuite(t *test.T) { 107 | suite.Run(t, &hashTestSuite{}) 108 | } 109 | -------------------------------------------------------------------------------- /test/mocks/Erc20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | Erc20 is a mock which records arguments passed to its methods as well as 5 | provides setters allowing us to dictate method return values 6 | */ 7 | 8 | pragma solidity >= 0.8.4; 9 | 10 | contract Erc20 { 11 | // a struct to hold the arguments passed to transferFrom 12 | struct TransferFromArgs { 13 | address to; 14 | uint256 amount; 15 | } 16 | 17 | // mapping for arguments passed to approve 18 | mapping (address => uint256) public approveCalled; 19 | // mapping of arguments sent to transfer. key is the passed in address. 20 | mapping (address => uint256) public transferCalled; 21 | mapping (address => address) public allowanceCalled; 22 | // mapping of arguments sent to transferFrom. key is passed from address. 23 | mapping (address => TransferFromArgs) public transferFromCalled; 24 | 25 | // balanceOf does not require a mapping. 26 | address private balanceOfCalled; 27 | 28 | string private nameReturn; 29 | string private symbolReturn; 30 | uint8 private decimalsReturn; 31 | // a boolean flag which allows us to dictate the return of approve(). 32 | bool private approveReturn; 33 | // a uint to return for balanceOf calls 34 | uint256 private balanceOfReturn; 35 | // what a call to allowance will return 36 | uint256 private allowanceReturn; 37 | // a boolean flag which allows us to dictate the return of transfer(). 38 | bool private transferReturn; 39 | // a boolean flag which allows us to dictate the return of transferFrom(). 40 | bool private transferFromReturn; 41 | 42 | function name() public view returns (string memory) { 43 | return nameReturn; 44 | } 45 | 46 | function nameReturns(string memory s) public { 47 | nameReturn = s; 48 | } 49 | 50 | function decimals() public view returns (uint8) { 51 | return decimalsReturn; 52 | } 53 | 54 | function decimalsReturns(uint8 n) public { 55 | decimalsReturn = n; 56 | } 57 | 58 | function symbol() public view returns (string memory) { 59 | return symbolReturn; 60 | } 61 | 62 | function symbolReturns(string memory s) public { 63 | symbolReturn = s; 64 | } 65 | 66 | function approve(address s, uint256 a) public returns (bool) { 67 | approveCalled[s] = a; 68 | return approveReturn; 69 | } 70 | 71 | function approveReturns(bool b) public { 72 | approveReturn = b; 73 | } 74 | 75 | function allowance(address o, address s) public returns (uint256) { 76 | allowanceCalled[o] = s; 77 | return allowanceReturn; 78 | } 79 | 80 | function allowanceReturns(uint256 n) public { 81 | allowanceReturn = n; 82 | } 83 | 84 | function balanceOfReturns(uint256 b) public { 85 | balanceOfReturn = b; 86 | } 87 | 88 | function balanceOf(address t) public returns (uint256) { 89 | balanceOfCalled = t; 90 | return balanceOfReturn; 91 | } 92 | 93 | function transfer(address t, uint256 a) public returns (bool) { 94 | transferCalled[t] = a; 95 | return transferReturn; 96 | } 97 | 98 | function transferReturns(bool b) public { 99 | transferReturn = b; 100 | } 101 | 102 | function transferFrom(address f, address t, uint256 a) public returns (bool) { 103 | TransferFromArgs memory args; 104 | args.to = t; 105 | args.amount = a; 106 | transferFromCalled[f] = args; 107 | return transferFromReturn; 108 | } 109 | 110 | function transferFromReturns(bool b) public { 111 | transferFromReturn = b; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/fakes/Hash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | /** 6 | @notice Encapsulation of the logic to produce EIP712 hashed domain and messages. 7 | Also to produce / verify hashed and signed Orders. 8 | See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md 9 | See/attribute https://github.com/0xProject/0x-monorepo/blob/development/contracts/utils/contracts/src/LibEIP712.sol 10 | */ 11 | 12 | library Hash { 13 | /// @dev struct represents the attributes of an offchain Swivel.Order 14 | struct Order { 15 | bytes32 key; 16 | address maker; 17 | address underlying; 18 | bool vault; 19 | bool exit; 20 | uint256 principal; 21 | uint256 premium; 22 | uint256 maturity; 23 | uint256 expiry; 24 | } 25 | 26 | // EIP712 Domain Separator typeHash 27 | // keccak256(abi.encodePacked( 28 | // 'EIP712Domain(', 29 | // 'string name,', 30 | // 'string version,', 31 | // 'uint256 chainId,', 32 | // 'address verifyingContract', 33 | // ')' 34 | // )); 35 | bytes32 constant internal DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; 36 | 37 | // EIP712 typeHash of an Order 38 | // keccak256(abi.encodePacked( 39 | // 'Order(', 40 | // 'bytes32 key,', 41 | // 'address maker,', 42 | // 'address underlying,', 43 | // 'bool vault,', 44 | // 'bool exit,', 45 | // 'uint256 principal,', 46 | // 'uint256 premium,', 47 | // 'uint256 maturity,', 48 | // 'uint256 expiry', 49 | // ')' 50 | // )); 51 | bytes32 constant internal ORDER_TYPEHASH = 0x7ddd38ab5ed1c16b61ca90eeb9579e29da1ba821cf42d8cdef8f30a31a6a4146; 52 | 53 | /// @param n EIP712 domain name 54 | /// @param version EIP712 semantic version string 55 | /// @param i Chain ID 56 | /// @param verifier address of the verifying contract 57 | function domain(string memory n, string memory version, uint256 i, address verifier) internal pure returns (bytes32) { 58 | bytes32 hash; 59 | 60 | assembly { 61 | let nameHash := keccak256(add(n, 32), mload(n)) 62 | let versionHash := keccak256(add(version, 32), mload(version)) 63 | let pointer := mload(64) 64 | mstore(pointer, DOMAIN_TYPEHASH) 65 | mstore(add(pointer, 32), nameHash) 66 | mstore(add(pointer, 64), versionHash) 67 | mstore(add(pointer, 96), i) 68 | mstore(add(pointer, 128), verifier) 69 | hash := keccak256(pointer, 160) 70 | } 71 | 72 | return hash; 73 | } 74 | 75 | /// @param d Type hash of the domain separator (see Hash.domain) 76 | /// @param h EIP712 hash struct (order for example) 77 | function message(bytes32 d, bytes32 h) internal pure returns (bytes32) { 78 | bytes32 hash; 79 | 80 | assembly { 81 | let pointer := mload(64) 82 | mstore(pointer, 0x1901000000000000000000000000000000000000000000000000000000000000) 83 | mstore(add(pointer, 2), d) 84 | mstore(add(pointer, 34), h) 85 | hash := keccak256(pointer, 66) 86 | } 87 | 88 | return hash; 89 | } 90 | 91 | /// @param o A Swivel Order 92 | function order(Order calldata o) internal pure returns (bytes32) { 93 | // TODO assembly 94 | return keccak256(abi.encode( 95 | ORDER_TYPEHASH, 96 | o.key, 97 | o.maker, 98 | o.underlying, 99 | o.vault, 100 | o.exit, 101 | o.principal, 102 | o.premium, 103 | o.maturity, 104 | o.expiry 105 | )); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/swivel/Hash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >= 0.8.4; 4 | 5 | /** 6 | @notice Encapsulation of the logic to produce EIP712 hashed domain and messages. 7 | Also to produce / verify hashed and signed Orders. 8 | See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md 9 | See/attribute https://github.com/0xProject/0x-monorepo/blob/development/contracts/utils/contracts/src/LibEIP712.sol 10 | */ 11 | 12 | library Hash { 13 | /// @dev struct represents the attributes of an offchain Swivel.Order 14 | struct Order { 15 | bytes32 key; 16 | address maker; 17 | address underlying; 18 | bool vault; 19 | bool exit; 20 | uint256 principal; 21 | uint256 premium; 22 | uint256 maturity; 23 | uint256 expiry; 24 | } 25 | 26 | // EIP712 Domain Separator typeHash 27 | // keccak256(abi.encodePacked( 28 | // 'EIP712Domain(', 29 | // 'string name,', 30 | // 'string version,', 31 | // 'uint256 chainId,', 32 | // 'address verifyingContract', 33 | // ')' 34 | // )); 35 | bytes32 constant internal DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; 36 | 37 | // EIP712 typeHash of an Order 38 | // keccak256(abi.encodePacked( 39 | // 'Order(', 40 | // 'bytes32 key,', 41 | // 'address maker,', 42 | // 'address underlying,', 43 | // 'bool vault,', 44 | // 'bool exit,', 45 | // 'uint256 principal,', 46 | // 'uint256 premium,', 47 | // 'uint256 maturity,', 48 | // 'uint256 expiry', 49 | // ')' 50 | // )); 51 | bytes32 constant internal ORDER_TYPEHASH = 0x7ddd38ab5ed1c16b61ca90eeb9579e29da1ba821cf42d8cdef8f30a31a6a4146; 52 | 53 | /// @param n EIP712 domain name 54 | /// @param version EIP712 semantic version string 55 | /// @param i Chain ID 56 | /// @param verifier address of the verifying contract 57 | function domain(string memory n, string memory version, uint256 i, address verifier) internal pure returns (bytes32) { 58 | bytes32 hash; 59 | 60 | assembly { 61 | let nameHash := keccak256(add(n, 32), mload(n)) 62 | let versionHash := keccak256(add(version, 32), mload(version)) 63 | let pointer := mload(64) 64 | mstore(pointer, DOMAIN_TYPEHASH) 65 | mstore(add(pointer, 32), nameHash) 66 | mstore(add(pointer, 64), versionHash) 67 | mstore(add(pointer, 96), i) 68 | mstore(add(pointer, 128), verifier) 69 | hash := keccak256(pointer, 160) 70 | } 71 | 72 | return hash; 73 | } 74 | 75 | /// @param d Type hash of the domain separator (see Hash.domain) 76 | /// @param h EIP712 hash struct (order for example) 77 | function message(bytes32 d, bytes32 h) internal pure returns (bytes32) { 78 | bytes32 hash; 79 | 80 | assembly { 81 | let pointer := mload(64) 82 | mstore(pointer, 0x1901000000000000000000000000000000000000000000000000000000000000) 83 | mstore(add(pointer, 2), d) 84 | mstore(add(pointer, 34), h) 85 | hash := keccak256(pointer, 66) 86 | } 87 | 88 | return hash; 89 | } 90 | 91 | /// @param o A Swivel Order 92 | function order(Order calldata o) internal pure returns (bytes32) { 93 | // TODO assembly 94 | return keccak256(abi.encode( 95 | ORDER_TYPEHASH, 96 | o.key, 97 | o.maker, 98 | o.underlying, 99 | o.vault, 100 | o.exit, 101 | o.principal, 102 | o.premium, 103 | o.maturity, 104 | o.expiry 105 | )); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/transfer_notional_fee_test.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/mocks" 11 | "github.com/swivel-finance/gost/test/vaulttracker" 12 | ) 13 | 14 | type transferNotionalFeeSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | CErc20 *mocks.CErc20Session 19 | VaultTracker *vaulttracker.VaultTrackerSession 20 | } 21 | 22 | func (s *transferNotionalFeeSuite) SetupTest() { 23 | var err error 24 | assert := assertions.New(s.T()) 25 | 26 | s.Env = NewEnv(big.NewInt(ONE_ETH)) 27 | s.Dep, err = Deploy(s.Env) 28 | assert.Nil(err) 29 | 30 | err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 31 | assert.Nil(err) 32 | s.Env.Blockchain.Commit() 33 | 34 | s.CErc20 = &mocks.CErc20Session{ 35 | Contract: s.Dep.CErc20, 36 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 37 | TransactOpts: bind.TransactOpts{ 38 | From: s.Env.Owner.Opts.From, 39 | Signer: s.Env.Owner.Opts.Signer, 40 | }, 41 | } 42 | 43 | // binding owner to both, kind of why it exists - but could be any of the env wallets 44 | s.VaultTracker = &vaulttracker.VaultTrackerSession{ 45 | Contract: s.Dep.VaultTracker, 46 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 47 | TransactOpts: bind.TransactOpts{ 48 | From: s.Env.Owner.Opts.From, 49 | Signer: s.Env.Owner.Opts.Signer, 50 | }, 51 | } 52 | } 53 | 54 | func (s *transferNotionalFeeSuite) TestTransferNotionalFee() { 55 | assert := assertions.New(s.T()) 56 | 57 | rate1 := big.NewInt(1) 58 | tx, err := s.CErc20.ExchangeRateCurrentReturns(rate1) 59 | assert.Nil(err) 60 | assert.NotNil(tx) 61 | s.Env.Blockchain.Commit() 62 | 63 | // add funds to the swivel vault so that exchangeRate can be set, can oven use 0 to simulate first one... 64 | // the reason this is here is to set the exchangeRate to the above as set to 0 in the deployment script 65 | // (the CErc20 is not mocked there) 66 | tx, err = s.VaultTracker.AddNotional(s.Dep.SwivelAddress, big.NewInt(0)) 67 | assert.Nil(err) 68 | assert.NotNil(tx) 69 | s.Env.Blockchain.Commit() 70 | 71 | // user needs funds in their vault 72 | userVaultAmt := big.NewInt(1000) 73 | userFee := big.NewInt(500) 74 | // swivel's new balance should be userFee as 1k - 500 is obvs 500... 75 | 76 | tx, err = s.VaultTracker.AddNotional(s.Env.User1.Opts.From, userVaultAmt) 77 | assert.Nil(err) 78 | assert.NotNil(tx) 79 | s.Env.Blockchain.Commit() 80 | 81 | // transfer from user to swivel, which will have no vault up til now... 82 | tx, err = s.VaultTracker.TransferNotionalFee(s.Env.User1.Opts.From, userFee) 83 | assert.Nil(err) 84 | assert.NotNil(tx) 85 | s.Env.Blockchain.Commit() 86 | 87 | swiVault, err := s.VaultTracker.Vaults(s.Dep.SwivelAddress) 88 | assert.Nil(err) 89 | assert.NotNil(swiVault) 90 | assert.Equal(swiVault.Notional, userFee) 91 | s.T().Log(swiVault.Notional) 92 | s.T().Log(swiVault.ExchangeRate) 93 | 94 | userVault, err := s.VaultTracker.Vaults(s.Env.User1.Opts.From) 95 | assert.Nil(err) 96 | assert.NotNil(userVault) 97 | assert.Equal(userFee, userVault.Notional) 98 | s.T().Log(userVault.Notional) 99 | s.T().Log(userVault.ExchangeRate) 100 | } 101 | 102 | func TestTrackerTransferNotionalFeeSuite(t *test.T) { 103 | suite.Run(t, &transferNotionalFeeSuite{}) 104 | } 105 | -------------------------------------------------------------------------------- /test/swivel/MarketPlace.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"burnZcTokenRemovingNotional","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"cTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"custodialExit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"custodialInitiate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"mintZcTokenAddingNotional","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"p2pVaultExchange","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"p2pZcTokenExchange","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"redeemVaultInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"redeemZcToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferVaultNotionalFee","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/mocks/Erc20.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"allowanceCalled","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"allowanceReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"approveCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"approveReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"t","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"b","type":"uint256"}],"name":"balanceOfReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"n","type":"uint8"}],"name":"decimalsReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"s","type":"string"}],"name":"nameReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"s","type":"string"}],"name":"symbolReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"transferCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"transferFromCalled","outputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"transferFromReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"transferReturns","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/tokens/Erc2612.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"string","name":"n","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"uint8","name":"d","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domain","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"d","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/fakes/SigFake.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b506107be806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063458b017a1461003b578063ecf888711461006d575b600080fd5b610055600480360381019061005091906103f4565b61009d565b60405161006493929190610472565b60405180910390f35b610087600480360381019061008291906104f9565b6100b8565b604051610094919061057a565b60405180910390f35b60008060006100ab846100cc565b9250925092509193909250565b60006100c48383610145565b905092915050565b60008060006041845114610115576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161010c906105f2565b60405180910390fd5b60008060006020870151925060408701519150606087015160001a90508083839550955095505050509193909250565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0826040013560001c11156101b1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a89061065e565b60405180910390fd5b601b8260000160208101906101c691906106aa565b60ff1614806101ea5750601c8260000160208101906101e591906106aa565b60ff16145b610229576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161022090610723565b60405180910390fd5b60018383600001602081019061023f91906106aa565b84602001358560400135604051600081526020016040526040516102669493929190610743565b6020604051602081039080840390855afa158015610288573d6000803e3d6000fd5b50505060206040510351905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610301826102b8565b810181811067ffffffffffffffff821117156103205761031f6102c9565b5b80604052505050565b600061033361029a565b905061033f82826102f8565b919050565b600067ffffffffffffffff82111561035f5761035e6102c9565b5b610368826102b8565b9050602081019050919050565b82818337600083830152505050565b600061039761039284610344565b610329565b9050828152602081018484840111156103b3576103b26102b3565b5b6103be848285610375565b509392505050565b600082601f8301126103db576103da6102ae565b5b81356103eb848260208601610384565b91505092915050565b60006020828403121561040a576104096102a4565b5b600082013567ffffffffffffffff811115610428576104276102a9565b5b610434848285016103c6565b91505092915050565b600060ff82169050919050565b6104538161043d565b82525050565b6000819050919050565b61046c81610459565b82525050565b6000606082019050610487600083018661044a565b6104946020830185610463565b6104a16040830184610463565b949350505050565b6104b281610459565b81146104bd57600080fd5b50565b6000813590506104cf816104a9565b92915050565b600080fd5b6000606082840312156104f0576104ef6104d5565b5b81905092915050565b600080608083850312156105105761050f6102a4565b5b600061051e858286016104c0565b925050602061052f858286016104da565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061056482610539565b9050919050565b61057481610559565b82525050565b600060208201905061058f600083018461056b565b92915050565b600082825260208201905092915050565b7f696e76616c6964207369676e6174757265206c656e6774680000000000000000600082015250565b60006105dc601883610595565b91506105e7826105a6565b602082019050919050565b6000602082019050818103600083015261060b816105cf565b9050919050565b7f696e76616c6964207369676e6174757265202273222076616c75650000000000600082015250565b6000610648601b83610595565b915061065382610612565b602082019050919050565b600060208201905081810360008301526106778161063b565b9050919050565b6106878161043d565b811461069257600080fd5b50565b6000813590506106a48161067e565b92915050565b6000602082840312156106c0576106bf6102a4565b5b60006106ce84828501610695565b91505092915050565b7f696e76616c6964207369676e6174757265202276222076616c75650000000000600082015250565b600061070d601b83610595565b9150610718826106d7565b602082019050919050565b6000602082019050818103600083015261073c81610700565b9050919050565b60006080820190506107586000830187610463565b610765602083018661044a565b6107726040830185610463565b61077f6060830184610463565b9594505050505056fea2646970667358221220d275839d5273315cf51836396a9ac4db2f8b87d22c144f24972043ffc7954fd264736f6c634300080d0033 -------------------------------------------------------------------------------- /test/marketplace/VaultTracker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /** 4 | VaultTracker is a mock which records arguments passed to its methods as well as 5 | provides setters allowing us to dictate method return values 6 | */ 7 | 8 | pragma solidity >= 0.8.4; 9 | 10 | contract VaultTracker { 11 | struct TransferNotionalFromArgs { 12 | address to; 13 | uint256 amount; 14 | } 15 | 16 | // mapping of arguments sent to addNotional. key is the passed in address. 17 | mapping (address => uint256) public addNotionalCalled; 18 | // mapping of arguments sent to removeNotional. key is the passed in address. 19 | mapping (address => uint256) public removeNotionalCalled; 20 | // mapping of arguments sent to transferNotionalFrom. key is the passed in address. 21 | mapping (address => TransferNotionalFromArgs) public transferNotionalFromCalled; 22 | // mapping of args sent to transferFee, key is the given payer's address 23 | mapping (address => uint256) public transferNotionalFeeCalled; 24 | 25 | address public cTokenAddr; 26 | address public swivel; 27 | address public redeemInterestCalled; 28 | uint256 public matureVaultCalled; 29 | 30 | uint256 private maturityReturn; 31 | uint256 private redeemInterestReturn; 32 | bool private matureVaultReturn; 33 | // a boolean flag which allows us to dictate the return of addNotional(). 34 | bool private addNotionalReturn; 35 | // a boolean flag which allows us to dictate the return of removeNotional(). 36 | bool private removeNotionalReturn; 37 | // a boolean flag which allows us to dictate the return of transferNotionalFrom(). 38 | bool private transferNotionalFromReturn; 39 | bool private transferNotionalFeeReturn; 40 | 41 | /// @param m maturity 42 | /// @param c cToken address 43 | /// @param s deployed swivel contract address 44 | constructor(uint256 m, address c, address s) { 45 | maturityReturn = m; 46 | cTokenAddr = c; 47 | swivel = s; 48 | } 49 | 50 | function redeemInterestReturns(uint256 a) public { 51 | redeemInterestReturn = a; 52 | } 53 | 54 | function redeemInterest(address o) public returns (uint256) { 55 | redeemInterestCalled = o; 56 | return redeemInterestReturn; 57 | } 58 | 59 | function maturityReturns(uint256 n) public { 60 | maturityReturn = n; 61 | } 62 | 63 | // override what would be the autogenerated getter... 64 | function maturity() public view returns (uint256) { 65 | return maturityReturn; 66 | } 67 | 68 | function matureVault(uint256 c) public returns (bool) { 69 | matureVaultCalled = c; 70 | return matureVaultReturn; 71 | } 72 | 73 | function matureVaultReturns(bool b) public { 74 | matureVaultReturn = b; 75 | } 76 | 77 | function addNotionalReturns(bool b) public { 78 | addNotionalReturn = b; 79 | } 80 | 81 | function addNotional(address o, uint256 a) public returns (bool) { 82 | addNotionalCalled[o] = a; 83 | return addNotionalReturn; 84 | } 85 | 86 | function removeNotionalReturns(bool b) public { 87 | removeNotionalReturn = b; 88 | } 89 | 90 | function removeNotional(address o, uint256 a) public returns (bool) { 91 | removeNotionalCalled[o] = a; 92 | return removeNotionalReturn; 93 | } 94 | 95 | function transferNotionalFromReturns(bool b) public { 96 | transferNotionalFromReturn = b; 97 | } 98 | 99 | function transferNotionalFrom(address f, address t, uint256 a) public returns (bool) { 100 | TransferNotionalFromArgs memory args; 101 | args.to = t; 102 | args.amount = a; 103 | transferNotionalFromCalled[f] = args; 104 | return transferNotionalFromReturn; 105 | } 106 | 107 | function transferNotionalFeeReturns(bool b) public { 108 | transferNotionalFeeReturn = b; 109 | } 110 | 111 | function transferNotionalFee(address f, uint256 a) public returns (bool) { 112 | transferNotionalFeeCalled[f] = a; 113 | return transferNotionalFeeReturn; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/marketplace/VaultTracker.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b5060405161084c38038061084c83398101604081905261002f91610082565b600892909255600480546001600160a01b039283166001600160a01b031991821617909155600580549290931691161790556100be565b80516001600160a01b038116811461007d57600080fd5b919050565b60008060006060848603121561009757600080fd5b835192506100a760208501610066565b91506100b560408501610066565b90509250925092565b61077f806100cd6000396000f3fe608060405234801561001057600080fd5b50600436106101825760003560e01c806382cac89c116100d8578063b7dd34831161008c578063d6cb2c0d11610066578063d6cb2c0d1461059f578063da3de9e9146105b2578063e590c362146105f157600080fd5b8063b7dd348314610518578063bbce238614610538578063d0b9d0321461055857600080fd5b8063a701da69116100bd578063a701da6914610476578063b326258d146104be578063b4c4a4c81461050557600080fd5b806382cac89c14610412578063a01cfffb1461043257600080fd5b80633cc314431161013a5780635dfe12ac116101145780635dfe12ac1461036d578063613a28d1146103b357806364ae3c9d146103f857600080fd5b80633cc31443146102fd5780633dfa1f41146103065780635c70b7c11461032657600080fd5b8063177946731161016b57806317794673146101ff57806319caf46c1461029c578063204f83f9146102f557600080fd5b8063012b264a146101875780630aa93b9b146101d1575b600080fd5b6005546101a79073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6101f16101df366004610686565b60016020526000908152604090205481565b6040519081526020016101c8565b61028c61020d3660046106a8565b60408051808201825273ffffffffffffffffffffffffffffffffffffffff93841681526020808201938452948416600090815260029095529320925183547fffffffffffffffffffffffff000000000000000000000000000000000000000016921691909117825551600190910155600a546301000000900460ff1690565b60405190151581526020016101c8565b6101f16102aa366004610686565b600680547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905560095490565b6008546101f1565b6101f160075481565b6101f1610314366004610686565b60006020819052908152604090205481565b61036b6103343660046106e4565b600a8054911515610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff909216919091179055565b005b61036b61037b3660046106e4565b600a805491151562010000027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff909216919091179055565b61028c6103c1366004610706565b73ffffffffffffffffffffffffffffffffffffffff91909116600090815260016020526040902055600a5462010000900460ff1690565b61028c610406366004610730565b600755600a5460ff1690565b6006546101a79073ffffffffffffffffffffffffffffffffffffffff1681565b61028c610440366004610706565b73ffffffffffffffffffffffffffffffffffffffff91909116600090815260208190526040902055600a54610100900460ff1690565b61036b6104843660046106e4565b600a8054911515640100000000027fffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffff909216919091179055565b61028c6104cc366004610706565b73ffffffffffffffffffffffffffffffffffffffff91909116600090815260036020526040902055600a54640100000000900460ff1690565b61036b610513366004610730565b600855565b6004546101a79073ffffffffffffffffffffffffffffffffffffffff1681565b6101f1610546366004610686565b60036020526000908152604090205481565b61036b6105663660046106e4565b600a80549115156301000000027fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffff909216919091179055565b61036b6105ad366004610730565b600955565b61036b6105c03660046106e4565b600a80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055565b6106316105ff366004610686565b6002602052600090815260409020805460019091015473ffffffffffffffffffffffffffffffffffffffff9091169082565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016101c8565b803573ffffffffffffffffffffffffffffffffffffffff8116811461068157600080fd5b919050565b60006020828403121561069857600080fd5b6106a18261065d565b9392505050565b6000806000606084860312156106bd57600080fd5b6106c68461065d565b92506106d46020850161065d565b9150604084013590509250925092565b6000602082840312156106f657600080fd5b813580151581146106a157600080fd5b6000806040838503121561071957600080fd5b6107228361065d565b946020939093013593505050565b60006020828403121561074257600080fd5b503591905056fea2646970667358221220640475d4ac1a877c92ce4ef61bf2870834ee6a8e5e565cb57e1e55db7f69233064736f6c634300080d0033 -------------------------------------------------------------------------------- /pkg/marketplacetesting/transfer_vault_notional_fee_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/marketplace" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | ) 13 | 14 | type vaultTransferFeeSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *marketplace.MarketPlaceSession 21 | } 22 | 23 | func (s *vaultTransferFeeSuite) SetupTest() { 24 | var err error 25 | assert := assertions.New(s.T()) 26 | 27 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 28 | s.Dep, err = Deploy(s.Env) 29 | assert.Nil(err) 30 | 31 | s.Erc20 = &mocks.Erc20Session{ 32 | Contract: s.Dep.Erc20, 33 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 34 | TransactOpts: bind.TransactOpts{ 35 | From: s.Env.Owner.Opts.From, 36 | Signer: s.Env.Owner.Opts.Signer, 37 | }, 38 | } 39 | 40 | s.CErc20 = &mocks.CErc20Session{ 41 | Contract: s.Dep.CErc20, 42 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 43 | TransactOpts: bind.TransactOpts{ 44 | From: s.Env.Owner.Opts.From, 45 | Signer: s.Env.Owner.Opts.Signer, 46 | }, 47 | } 48 | 49 | s.MarketPlace = &marketplace.MarketPlaceSession{ 50 | Contract: s.Dep.MarketPlace, 51 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 52 | TransactOpts: bind.TransactOpts{ 53 | From: s.Env.Owner.Opts.From, 54 | Signer: s.Env.Owner.Opts.Signer, 55 | }, 56 | } 57 | 58 | // the swivel address must be set, using owner here for ease of mocking 59 | _, err = s.MarketPlace.SetSwivelAddress(s.Env.Owner.Opts.From) 60 | assert.Nil(err) 61 | s.Env.Blockchain.Commit() 62 | } 63 | 64 | func (s *vaultTransferFeeSuite) TestVaultTransferFee() { 65 | assert := assertions.New(s.T()) 66 | 67 | ownerOpts := s.Env.Owner.Opts 68 | user1Opts := s.Env.User1.Opts 69 | 70 | // make a market so that a vault is deployed 71 | underlying := s.Dep.Erc20Address 72 | maturity := big.NewInt(1234567) 73 | ctoken := s.Dep.CErc20Address 74 | 75 | tx, err := s.Erc20.DecimalsReturns(uint8(18)) 76 | assert.Nil(err) 77 | assert.NotNil(tx) 78 | s.Env.Blockchain.Commit() 79 | 80 | tx, err = s.CErc20.UnderlyingReturns(underlying) 81 | assert.Nil(err) 82 | assert.NotNil(tx) 83 | s.Env.Blockchain.Commit() 84 | 85 | tx, err = s.MarketPlace.CreateMarket( 86 | maturity, 87 | ctoken, 88 | "awesome market", 89 | "AM", 90 | ) 91 | 92 | assert.Nil(err) 93 | assert.NotNil(tx) 94 | s.Env.Blockchain.Commit() 95 | 96 | // find that vault, wrap it in a mock... 97 | market, err := s.MarketPlace.Markets(underlying, maturity) 98 | assert.Nil(err) 99 | assert.Equal(market.CTokenAddr, ctoken) 100 | 101 | vaultTrackerContract, err := mocks.NewVaultTracker(market.VaultAddr, s.Env.Blockchain) 102 | vaultTracker := &mocks.VaultTrackerSession{ 103 | Contract: vaultTrackerContract, 104 | CallOpts: bind.CallOpts{From: ownerOpts.From, Pending: false}, 105 | TransactOpts: bind.TransactOpts{ 106 | From: ownerOpts.From, 107 | Signer: ownerOpts.Signer, 108 | }, 109 | } 110 | 111 | // stub the mock vaulttracker... 112 | tx, err = vaultTracker.TransferNotionalFeeReturns(true) 113 | assert.Nil(err) 114 | assert.NotNil(tx) 115 | s.Env.Blockchain.Commit() 116 | 117 | amount := big.NewInt(100) 118 | 119 | tx, err = s.MarketPlace.TransferVaultNotionalFee(underlying, maturity, user1Opts.From, amount) 120 | assert.Nil(err) 121 | assert.NotNil(tx) 122 | 123 | s.Env.Blockchain.Commit() 124 | 125 | // marketplace should simply have passed the args thru... 126 | transferFee, err := vaultTracker.TransferNotionalFeeCalled(user1Opts.From) 127 | assert.Nil(err) 128 | assert.Equal(big.NewInt(100), transferFee) 129 | } 130 | 131 | func TestVaultTransferFeeSuite(t *test.T) { 132 | suite.Run(t, &vaultTransferFeeSuite{}) 133 | } 134 | -------------------------------------------------------------------------------- /test/marketplace/VaultTracker.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"address","name":"c","type":"address"},{"internalType":"address","name":"s","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"addNotional","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"addNotionalCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"addNotionalReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cTokenAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"c","type":"uint256"}],"name":"matureVault","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"matureVaultCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"matureVaultReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maturity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"maturityReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"}],"name":"redeemInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"redeemInterestCalled","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"}],"name":"redeemInterestReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"removeNotional","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"removeNotionalCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"removeNotionalReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swivel","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferNotionalFee","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"transferNotionalFeeCalled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"transferNotionalFeeReturns","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferNotionalFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"transferNotionalFromCalled","outputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"b","type":"bool"}],"name":"transferNotionalFromReturns","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /pkg/swiveltesting/redeem_zctoken_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/mocks" 11 | "github.com/swivel-finance/gost/test/swivel" 12 | ) 13 | 14 | type redeemZcTokenSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *mocks.MarketPlaceSession 21 | Swivel *swivel.SwivelSession 22 | } 23 | 24 | func (s *redeemZcTokenSuite) SetupTest() { 25 | var err error 26 | 27 | s.Env = NewEnv(big.NewInt(ONE_ETH)) 28 | s.Dep, err = Deploy(s.Env) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | s.Erc20 = &mocks.Erc20Session{ 35 | Contract: s.Dep.Erc20, 36 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 37 | TransactOpts: bind.TransactOpts{ 38 | From: s.Env.Owner.Opts.From, 39 | Signer: s.Env.Owner.Opts.Signer, 40 | }, 41 | } 42 | 43 | s.CErc20 = &mocks.CErc20Session{ 44 | Contract: s.Dep.CErc20, 45 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 46 | TransactOpts: bind.TransactOpts{ 47 | From: s.Env.Owner.Opts.From, 48 | Signer: s.Env.Owner.Opts.Signer, 49 | }, 50 | } 51 | 52 | s.MarketPlace = &mocks.MarketPlaceSession{ 53 | Contract: s.Dep.MarketPlace, 54 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 55 | TransactOpts: bind.TransactOpts{ 56 | From: s.Env.Owner.Opts.From, 57 | Signer: s.Env.Owner.Opts.Signer, 58 | }, 59 | } 60 | 61 | s.Swivel = &swivel.SwivelSession{ 62 | Contract: s.Dep.Swivel, 63 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 64 | TransactOpts: bind.TransactOpts{ 65 | From: s.Env.Owner.Opts.From, 66 | Signer: s.Env.Owner.Opts.Signer, 67 | }, 68 | } 69 | } 70 | 71 | func (s *redeemZcTokenSuite) TestRedeemZcToken() { 72 | assert := assertions.New(s.T()) 73 | underlying := s.Dep.Erc20Address 74 | maturity := s.Dep.Maturity 75 | 76 | // stub the underlying to return true or the Safe lib will revert 77 | tx, err := s.Erc20.TransferReturns(true) 78 | assert.NotNil(tx) 79 | assert.Nil(err) 80 | 81 | tx, err = s.CErc20.RedeemUnderlyingReturns(big.NewInt(0)) 82 | assert.NotNil(tx) 83 | assert.Nil(err) 84 | 85 | s.Env.Blockchain.Commit() 86 | 87 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 88 | assert.Nil(err) 89 | assert.NotNil(tx) 90 | 91 | s.Env.Blockchain.Commit() 92 | 93 | // stub the marketplace mock.redeemZcToken 94 | redeemed := big.NewInt(12345) 95 | tx, err = s.MarketPlace.RedeemZcTokenReturns(redeemed) 96 | assert.Nil(err) 97 | assert.NotNil(tx) 98 | 99 | s.Env.Blockchain.Commit() 100 | 101 | amount := big.NewInt(123456) 102 | tx, err = s.Swivel.RedeemZcToken(underlying, maturity, amount) 103 | assert.Nil(err) 104 | assert.NotNil(tx) 105 | 106 | s.Env.Blockchain.Commit() 107 | 108 | // underlying tranfer should have been called with an amount redeemed 109 | transferred, err := s.Erc20.TransferCalled(s.Env.Owner.Opts.From) 110 | assert.Nil(err) 111 | assert.Equal(redeemed, transferred) 112 | } 113 | 114 | func (s *redeemZcTokenSuite) TestRedeemZcTokenRedeemUnderlyingFails() { 115 | assert := assertions.New(s.T()) 116 | underlying := s.Dep.Erc20Address 117 | maturity := s.Dep.Maturity 118 | 119 | tx, err := s.CErc20.RedeemUnderlyingReturns(big.NewInt(1)) 120 | assert.NotNil(tx) 121 | assert.Nil(err) 122 | 123 | s.Env.Blockchain.Commit() 124 | 125 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 126 | assert.Nil(err) 127 | assert.NotNil(tx) 128 | s.Env.Blockchain.Commit() 129 | 130 | // stub the marketplace mock.redeemZcToken 131 | tx, err = s.MarketPlace.RedeemZcTokenReturns(big.NewInt(12345)) 132 | assert.Nil(err) 133 | assert.NotNil(tx) 134 | 135 | s.Env.Blockchain.Commit() 136 | 137 | amount := big.NewInt(123456) 138 | tx, err = s.Swivel.RedeemZcToken(underlying, maturity, amount) 139 | assert.NotNil(err) 140 | assert.Regexp("compound redemption failed", err.Error()) 141 | assert.Nil(tx) 142 | } 143 | 144 | func TestRedeemZcTokenSuite(t *test.T) { 145 | suite.Run(t, &redeemZcTokenSuite{}) 146 | } 147 | -------------------------------------------------------------------------------- /pkg/vaulttrackertesting/transfer_notional_from_test.go: -------------------------------------------------------------------------------- 1 | package vaulttrackertesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/mocks" 11 | "github.com/swivel-finance/gost/test/vaulttracker" 12 | ) 13 | 14 | type transferNotionalFromSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | CErc20 *mocks.CErc20Session 19 | VaultTracker *vaulttracker.VaultTrackerSession // *Session objects are created by the go bindings 20 | } 21 | 22 | func (s *transferNotionalFromSuite) SetupTest() { 23 | var err error 24 | 25 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 26 | s.Dep, err = Deploy(s.Env) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 32 | if err != nil { 33 | panic(err) 34 | } 35 | s.Env.Blockchain.Commit() 36 | 37 | s.CErc20 = &mocks.CErc20Session{ 38 | Contract: s.Dep.CErc20, 39 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 40 | TransactOpts: bind.TransactOpts{ 41 | From: s.Env.Owner.Opts.From, 42 | Signer: s.Env.Owner.Opts.Signer, 43 | }, 44 | } 45 | 46 | // binding owner to both, kind of why it exists - but could be any of the env wallets 47 | s.VaultTracker = &vaulttracker.VaultTrackerSession{ 48 | Contract: s.Dep.VaultTracker, 49 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 50 | TransactOpts: bind.TransactOpts{ 51 | From: s.Env.Owner.Opts.From, 52 | Signer: s.Env.Owner.Opts.Signer, 53 | }, 54 | } 55 | } 56 | 57 | func (s *transferNotionalFromSuite) TestTransferNotionalFrom() { 58 | assert := assertions.New(s.T()) 59 | 60 | rate1 := big.NewInt(123456789) 61 | tx, err := s.CErc20.ExchangeRateCurrentReturns(rate1) 62 | assert.Nil(err) 63 | assert.NotNil(tx) 64 | s.Env.Blockchain.Commit() 65 | 66 | // call AddNotional for Owner with no ownerVault 67 | amount1 := big.NewInt(1000) 68 | tx, err = s.VaultTracker.AddNotional(s.Env.Owner.Opts.From, amount1) 69 | assert.Nil(err) 70 | assert.NotNil(tx) 71 | 72 | s.Env.Blockchain.Commit() 73 | 74 | // call AddNotional for User1 with no ownerVault 75 | amount2 := big.NewInt(1000) 76 | tx, err = s.VaultTracker.AddNotional(s.Env.User1.Opts.From, amount2) 77 | assert.Nil(err) 78 | assert.NotNil(tx) 79 | 80 | s.Env.Blockchain.Commit() 81 | 82 | tx, err = s.VaultTracker.TransferNotionalFrom(s.Env.Owner.Opts.From, s.Env.User1.Opts.From, big.NewInt(100)) 83 | assert.Nil(err) 84 | assert.NotNil(tx) 85 | 86 | s.Env.Blockchain.Commit() 87 | 88 | ownerVault, err := s.VaultTracker.Vaults(s.Env.Owner.Opts.From) 89 | assert.Nil(err) 90 | assert.NotNil(ownerVault) 91 | assert.Equal(big.NewInt(900), ownerVault.Notional) 92 | assert.Equal(rate1, ownerVault.ExchangeRate) 93 | assert.Equal(ownerVault.Redeemable.Cmp(ZERO), 0) 94 | 95 | user1Vault, err := s.VaultTracker.Vaults(s.Env.User1.Opts.From) 96 | assert.Nil(err) 97 | assert.NotNil(user1Vault) 98 | assert.Equal(big.NewInt(1100), user1Vault.Notional) 99 | assert.Equal(rate1, user1Vault.ExchangeRate) 100 | assert.Equal(user1Vault.Redeemable.Cmp(ZERO), 0) 101 | } 102 | 103 | func (s *transferNotionalFromSuite) TestTransferNotionalFromAmountExceedsFail() { 104 | assert := assertions.New(s.T()) 105 | 106 | rate1 := big.NewInt(123456789) 107 | tx, err := s.CErc20.ExchangeRateCurrentReturns(rate1) 108 | assert.Nil(err) 109 | assert.NotNil(tx) 110 | s.Env.Blockchain.Commit() 111 | 112 | // call AddNotional for Owner with no ownerVault 113 | amount1 := big.NewInt(1000) 114 | tx, err = s.VaultTracker.AddNotional(s.Env.Owner.Opts.From, amount1) 115 | assert.Nil(err) 116 | assert.NotNil(tx) 117 | 118 | s.Env.Blockchain.Commit() 119 | 120 | // call AddNotional for User1 with no ownerVault 121 | amount2 := big.NewInt(1000) 122 | tx, err = s.VaultTracker.AddNotional(s.Env.User1.Opts.From, amount2) 123 | assert.Nil(err) 124 | assert.NotNil(tx) 125 | 126 | s.Env.Blockchain.Commit() 127 | 128 | tx, err = s.VaultTracker.TransferNotionalFrom(s.Env.Owner.Opts.From, s.Env.User1.Opts.From, big.NewInt(2000)) 129 | assert.NotNil(err) 130 | assert.Regexp("amount exceeds available balance", err.Error()) 131 | assert.Nil(tx) 132 | 133 | s.Env.Blockchain.Commit() 134 | } 135 | 136 | func TestTrackerTransferNotionalFromSuite(t *test.T) { 137 | suite.Run(t, &transferNotionalFromSuite{}) 138 | } 139 | -------------------------------------------------------------------------------- /test/tokens/ZcToken.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"string","name":"n","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"uint8","name":"d","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"s","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"f","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domain","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maturity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"o","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"d","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"s","type":"address"},{"internalType":"address","name":"r","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"underlying","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /pkg/swiveltesting/withdrawal_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | test "testing" 7 | "time" 8 | 9 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/suite" 13 | "github.com/swivel-finance/gost/test/mocks" 14 | "github.com/swivel-finance/gost/test/swivel" 15 | ) 16 | 17 | type withdrawalSuite struct { 18 | suite.Suite 19 | Env *Env 20 | Dep *Dep 21 | Erc20 *mocks.Erc20Session 22 | Swivel *swivel.SwivelSession 23 | } 24 | 25 | func (s *withdrawalSuite) SetupTest() { 26 | var err error 27 | 28 | s.Env = NewEnv(big.NewInt(ONE_ETH)) 29 | s.Dep, err = Deploy(s.Env) 30 | 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | // err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 36 | // if err != nil { 37 | // panic(err) 38 | // } 39 | // s.Env.Blockchain.Commit() 40 | 41 | s.Erc20 = &mocks.Erc20Session{ 42 | Contract: s.Dep.Erc20, 43 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 44 | TransactOpts: bind.TransactOpts{ 45 | From: s.Env.Owner.Opts.From, 46 | Signer: s.Env.Owner.Opts.Signer, 47 | }, 48 | } 49 | 50 | s.Swivel = &swivel.SwivelSession{ 51 | Contract: s.Dep.Swivel, 52 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 53 | TransactOpts: bind.TransactOpts{ 54 | From: s.Env.Owner.Opts.From, 55 | Signer: s.Env.Owner.Opts.Signer, 56 | }, 57 | } 58 | } 59 | 60 | func (s *withdrawalSuite) TestScheduleWithdrawal() { 61 | assert := assert.New(s.T()) 62 | 63 | tokenAddress := common.HexToAddress("0xiamatoken") // we don't need an actual token here 64 | 65 | tx, err := s.Swivel.ScheduleWithdrawal(tokenAddress) 66 | 67 | assert.Nil(err) 68 | assert.NotNil(tx) 69 | s.Env.Blockchain.Commit() 70 | 71 | hold, _ := s.Swivel.Withdrawals(tokenAddress) 72 | assert.Equal(1, hold.Cmp(big.NewInt(259200))) // hold should be greater than the hold constant 73 | } 74 | 75 | func (s *withdrawalSuite) TestScheduleWithdrawalFails() { 76 | assert := assert.New(s.T()) 77 | 78 | tokenAddress := common.HexToAddress("0xiamanothertoken") 79 | 80 | tx, err := s.Dep.Swivel.ScheduleWithdrawal(&bind.TransactOpts{From: s.Env.User1.Opts.From, Signer: s.Env.User1.Opts.Signer}, tokenAddress) 81 | 82 | assert.NotNil(err) 83 | assert.Nil(tx) 84 | assert.True(strings.Contains(err.Error(), "sender must be authorized")) 85 | s.Env.Blockchain.Commit() 86 | 87 | hold, _ := s.Swivel.Withdrawals(tokenAddress) 88 | assert.Equal(0, hold.Cmp(big.NewInt(0))) 89 | } 90 | 91 | func (s *withdrawalSuite) TestWithdrawalFailsNotScheduled() { 92 | assert := assert.New(s.T()) 93 | 94 | tokenAddress := common.HexToAddress("0xyomommastoken") 95 | 96 | tx, err := s.Swivel.Withdraw(tokenAddress) 97 | 98 | assert.NotNil(err) 99 | assert.Nil(tx) 100 | assert.True(strings.Contains(err.Error(), "no withdrawal scheduled")) 101 | } 102 | 103 | func (s *withdrawalSuite) TestWithdrawalFailsOnHold() { 104 | assert := assert.New(s.T()) 105 | 106 | tokenAddress := common.HexToAddress("0xspamtoken") 107 | 108 | tx, err := s.Swivel.ScheduleWithdrawal(tokenAddress) 109 | 110 | assert.Nil(err) 111 | assert.NotNil(tx) 112 | s.Env.Blockchain.Commit() 113 | 114 | tx, err = s.Swivel.Withdraw(tokenAddress) 115 | 116 | assert.NotNil(err) 117 | assert.Nil(tx) 118 | assert.True(strings.Contains(err.Error(), "withdrawal still on hold")) 119 | } 120 | 121 | func (s *withdrawalSuite) TestWithdrawal() { 122 | assert := assert.New(s.T()) 123 | 124 | // stub the underlying to return true or the Safe lib will revert 125 | tx, err := s.Erc20.TransferReturns(true) 126 | assert.NotNil(tx) 127 | assert.Nil(err) 128 | 129 | // stub the balanceOf return 130 | oneBill := big.NewInt(1000000000) 131 | tx, err = s.Erc20.BalanceOfReturns(oneBill) 132 | assert.NotNil(tx) 133 | assert.Nil(err) 134 | 135 | s.Env.Blockchain.Commit() 136 | 137 | // reset time to 0 138 | err = s.Env.Blockchain.AdjustTime(0) 139 | if err != nil { 140 | panic(err) 141 | } 142 | 143 | s.Env.Blockchain.Commit() 144 | 145 | // schedule it... 146 | tx, err = s.Swivel.ScheduleWithdrawal(s.Dep.Erc20Address) 147 | s.Env.Blockchain.Commit() 148 | 149 | // move, at least, to the hold time 150 | err = s.Env.Blockchain.AdjustTime(259200 * time.Second) 151 | if err != nil { 152 | panic(err) 153 | } 154 | s.Env.Blockchain.Commit() 155 | 156 | // now you should be able to withdraw 157 | tx, err = s.Swivel.Withdraw(s.Dep.Erc20Address) 158 | assert.Nil(err) 159 | assert.NotNil(tx) 160 | 161 | s.Env.Blockchain.Commit() 162 | 163 | // inspect the transfer amt 164 | amount, _ := s.Erc20.TransferCalled(s.Env.Owner.Opts.From) 165 | assert.Equal(amount, oneBill) 166 | } 167 | 168 | func TestWithdrawalSuite(t *test.T) { 169 | suite.Run(t, &withdrawalSuite{}) 170 | } 171 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/transfer_vault_notional_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/marketplace" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | ) 13 | 14 | type vaultTransferSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 21 | } 22 | 23 | func (s *vaultTransferSuite) SetupTest() { 24 | var err error 25 | assert := assertions.New(s.T()) 26 | 27 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 28 | s.Dep, err = Deploy(s.Env) 29 | assert.Nil(err) 30 | 31 | s.Erc20 = &mocks.Erc20Session{ 32 | Contract: s.Dep.Erc20, 33 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 34 | TransactOpts: bind.TransactOpts{ 35 | From: s.Env.Owner.Opts.From, 36 | Signer: s.Env.Owner.Opts.Signer, 37 | }, 38 | } 39 | 40 | s.CErc20 = &mocks.CErc20Session{ 41 | Contract: s.Dep.CErc20, 42 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 43 | TransactOpts: bind.TransactOpts{ 44 | From: s.Env.Owner.Opts.From, 45 | Signer: s.Env.Owner.Opts.Signer, 46 | }, 47 | } 48 | 49 | s.MarketPlace = &marketplace.MarketPlaceSession{ 50 | Contract: s.Dep.MarketPlace, 51 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 52 | TransactOpts: bind.TransactOpts{ 53 | From: s.Env.Owner.Opts.From, 54 | Signer: s.Env.Owner.Opts.Signer, 55 | }, 56 | } 57 | 58 | // the swivel address must be set 59 | _, err = s.MarketPlace.SetSwivelAddress(s.Dep.SwivelAddress) 60 | assert.Nil(err) 61 | s.Env.Blockchain.Commit() 62 | } 63 | 64 | func (s *vaultTransferSuite) TestTransferFailsWhenPaused() { 65 | assert := assertions.New(s.T()) 66 | 67 | underlying := s.Dep.Erc20Address 68 | maturity := big.NewInt(123456789) 69 | 70 | tx, err := s.MarketPlace.Pause(true) 71 | assert.Nil(err) 72 | assert.NotNil(tx) 73 | s.Env.Blockchain.Commit() 74 | 75 | amount := big.NewInt(100) 76 | tx, err = s.MarketPlace.TransferVaultNotional(underlying, maturity, s.Env.User1.Opts.From, amount) 77 | assert.Nil(tx) 78 | assert.NotNil(err) 79 | assert.Regexp("markets are paused", err.Error()) 80 | 81 | // unpause so the other tests don't fail 82 | tx, err = s.MarketPlace.Pause(false) 83 | assert.Nil(err) 84 | assert.NotNil(tx) 85 | s.Env.Blockchain.Commit() 86 | } 87 | 88 | func (s *vaultTransferSuite) TestVaultTransfer() { 89 | assert := assertions.New(s.T()) 90 | 91 | ownerOpts := s.Env.Owner.Opts 92 | user1Opts := s.Env.User1.Opts 93 | 94 | // make a market so that a vault is deployed 95 | underlying := s.Dep.Erc20Address 96 | maturity := big.NewInt(1234567) 97 | ctoken := s.Dep.CErc20Address 98 | 99 | tx, err := s.Erc20.DecimalsReturns(uint8(18)) 100 | assert.Nil(err) 101 | assert.NotNil(tx) 102 | s.Env.Blockchain.Commit() 103 | 104 | tx, err = s.CErc20.UnderlyingReturns(underlying) 105 | assert.Nil(err) 106 | assert.NotNil(tx) 107 | s.Env.Blockchain.Commit() 108 | 109 | tx, err = s.MarketPlace.CreateMarket( 110 | maturity, 111 | ctoken, 112 | "awesome market", 113 | "AM", 114 | ) 115 | 116 | assert.Nil(err) 117 | assert.NotNil(tx) 118 | s.Env.Blockchain.Commit() 119 | 120 | // find that vault, wrap it in a mock... 121 | market, err := s.MarketPlace.Markets(underlying, maturity) 122 | assert.Nil(err) 123 | assert.Equal(market.CTokenAddr, ctoken) 124 | 125 | vaultTrackerContract, err := mocks.NewVaultTracker(market.VaultAddr, s.Env.Blockchain) 126 | vaultTracker := &mocks.VaultTrackerSession{ 127 | Contract: vaultTrackerContract, 128 | CallOpts: bind.CallOpts{From: ownerOpts.From, Pending: false}, 129 | TransactOpts: bind.TransactOpts{ 130 | From: ownerOpts.From, 131 | Signer: ownerOpts.Signer, 132 | }, 133 | } 134 | 135 | // stub the mock vaulttracker... 136 | tx, err = vaultTracker.TransferNotionalFromReturns(true) 137 | assert.Nil(err) 138 | assert.NotNil(tx) 139 | s.Env.Blockchain.Commit() 140 | 141 | amount := big.NewInt(100) 142 | 143 | tx, err = s.MarketPlace.TransferVaultNotional(underlying, maturity, user1Opts.From, amount) 144 | assert.Nil(err) 145 | assert.NotNil(tx) 146 | 147 | s.Env.Blockchain.Commit() 148 | 149 | // marketplace should have called transfer with the msg.sender as owner 150 | transferArgs, err := vaultTracker.TransferNotionalFromCalled(ownerOpts.From) 151 | assert.Nil(err) 152 | assert.Equal(user1Opts.From, transferArgs.To) 153 | assert.Equal(amount, transferArgs.Amount) 154 | } 155 | 156 | func TestVaultTransferSuite(t *test.T) { 157 | suite.Run(t, &vaultTransferSuite{}) 158 | } 159 | -------------------------------------------------------------------------------- /test/marketplace/ZcToken.bin: -------------------------------------------------------------------------------- 1 | 60806040523480156200001157600080fd5b5060405162000a3938038062000a39833981016040819052620000349162000217565b60058054610100600160a81b0319166101006001600160a01b03881602179055600684905582516200006e906003906020860190620000a4565b50815162000084906004906020850190620000a4565b506005805460ff191660ff92909216919091179055506200030392505050565b828054620000b290620002c7565b90600052602060002090601f016020900481019282620000d6576000855562000121565b82601f10620000f157805160ff191683800117855562000121565b8280016001018555821562000121579182015b828111156200012157825182559160200191906001019062000104565b506200012f92915062000133565b5090565b5b808211156200012f576000815560010162000134565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200017257600080fd5b81516001600160401b03808211156200018f576200018f6200014a565b604051601f8301601f19908116603f01168101908282118183101715620001ba57620001ba6200014a565b81604052838152602092508683858801011115620001d757600080fd5b600091505b83821015620001fb5785820183015181830184015290820190620001dc565b838211156200020d5760008385830101525b9695505050505050565b600080600080600060a086880312156200023057600080fd5b85516001600160a01b03811681146200024857600080fd5b6020870151604088015191965094506001600160401b03808211156200026d57600080fd5b6200027b89838a0162000160565b945060608801519150808211156200029257600080fd5b50620002a18882890162000160565b925050608086015160ff81168114620002b957600080fd5b809150509295509295909350565b600181811c90821680620002dc57607f821691505b602082108103620002fd57634e487b7160e01b600052602260045260246000fd5b50919050565b61072680620003136000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c80639dc29fac11610097578063e541efa211610066578063e541efa214610378578063e7ba6774146103e4578063ee4db5701461043e578063fdfe5f4d1461045e57600080fd5b80639dc29fac146102c7578063b4c4a4c814610306578063b9bb928c14610319578063bba0ad391461035857600080fd5b806340c10f19116100d357806340c10f19146101f05780636521b96a146102345780636f307dc31461027c57806395d89b41146102bf57600080fd5b806306fdde0314610105578063204f83f91461012357806323b872dd14610135578063313ce567146101d1575b600080fd5b61010d6104a3565b60405161011a919061053e565b60405180910390f35b6006545b60405190815260200161011a565b6101c16101433660046105da565b60408051808201825273ffffffffffffffffffffffffffffffffffffffff93841681526020808201938452948416600090815260029095529320925183547fffffffffffffffffffffffff00000000000000000000000000000000000000001692169190911782555160019091015560075462010000900460ff1690565b604051901515815260200161011a565b6005546101de9060ff1681565b60405160ff909116815260200161011a565b6101c16101fe366004610616565b73ffffffffffffffffffffffffffffffffffffffff91909116600090815260016020526040902055600754610100900460ff1690565b61027a610242366004610640565b6007805491151562010000027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff909216919091179055565b005b600554610100900473ffffffffffffffffffffffffffffffffffffffff1660405173ffffffffffffffffffffffffffffffffffffffff909116815260200161011a565b61010d610531565b6101c16102d5366004610616565b73ffffffffffffffffffffffffffffffffffffffff9190911660009081526020819052604090205560075460ff1690565b61027a610314366004610669565b600655565b61027a610327366004610640565b600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055565b610127610366366004610682565b60006020819052908152604090205481565b6103b8610386366004610682565b6002602052600090815260409020805460019091015473ffffffffffffffffffffffffffffffffffffffff9091169082565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260208301919091520161011a565b61027a6103f2366004610682565b6005805473ffffffffffffffffffffffffffffffffffffffff909216610100027fffffffffffffffffffffff0000000000000000000000000000000000000000ff909216919091179055565b61012761044c366004610682565b60016020526000908152604090205481565b61027a61046c366004610640565b60078054911515610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff909216919091179055565b600380546104b09061069d565b80601f01602080910402602001604051908101604052809291908181526020018280546104dc9061069d565b80156105295780601f106104fe57610100808354040283529160200191610529565b820191906000526020600020905b81548152906001019060200180831161050c57829003601f168201915b505050505081565b600480546104b09061069d565b600060208083528351808285015260005b8181101561056b5785810183015185820160400152820161054f565b8181111561057d576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146105d557600080fd5b919050565b6000806000606084860312156105ef57600080fd5b6105f8846105b1565b9250610606602085016105b1565b9150604084013590509250925092565b6000806040838503121561062957600080fd5b610632836105b1565b946020939093013593505050565b60006020828403121561065257600080fd5b8135801515811461066257600080fd5b9392505050565b60006020828403121561067b57600080fd5b5035919050565b60006020828403121561069457600080fd5b610662826105b1565b600181811c908216806106b157607f821691505b6020821081036106ea577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b5091905056fea2646970667358221220ff6ec97c20029c90c9588c834bd97f6b228ad123760e381a334aa9ff7d6af51a64736f6c634300080d0033 -------------------------------------------------------------------------------- /test/swivel/Safe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | // Adapted from: https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol 3 | pragma solidity >= 0.8.4; 4 | 5 | import {Erc20} from "./Interfaces.sol"; 6 | /** 7 | @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. 8 | @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol) 9 | @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. 10 | */ 11 | 12 | library Safe { 13 | /// @param e Erc20 token to execute the call with 14 | /// @param t To address 15 | /// @param a Amount being transferred 16 | function approve(Erc20 e, address t, uint256 a) internal { 17 | bool result; 18 | 19 | assembly { 20 | // Get a pointer to some free memory. 21 | let pointer := mload(0x40) 22 | 23 | // Write the abi-encoded calldata to memory piece by piece: 24 | mstore(pointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) // Begin with the function selector. 25 | mstore(add(pointer, 4), and(t, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. 26 | mstore(add(pointer, 36), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value. 27 | 28 | // Call the token and store if it succeeded or not. 29 | // We use 68 because the calldata length is 4 + 32 * 2. 30 | result := call(gas(), e, 0, pointer, 68, 0, 0) 31 | } 32 | 33 | require(success(result), "approve failed"); 34 | } 35 | 36 | /// @param e Erc20 token to execute the call with 37 | /// @param t To address 38 | /// @param a Amount being transferred 39 | function transfer(Erc20 e, address t, uint256 a) internal { 40 | bool result; 41 | 42 | assembly { 43 | // Get a pointer to some free memory. 44 | let pointer := mload(0x40) 45 | 46 | // Write the abi-encoded calldata to memory piece by piece: 47 | mstore(pointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // Begin with the function selector. 48 | mstore(add(pointer, 4), and(t, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. 49 | mstore(add(pointer, 36), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value. 50 | 51 | // Call the token and store if it succeeded or not. 52 | // We use 68 because the calldata length is 4 + 32 * 2. 53 | result := call(gas(), e, 0, pointer, 68, 0, 0) 54 | } 55 | 56 | require(success(result), "transfer failed"); 57 | } 58 | 59 | /// @param e Erc20 token to execute the call with 60 | /// @param f From address 61 | /// @param t To address 62 | /// @param a Amount being transferred 63 | function transferFrom(Erc20 e, address f, address t, uint256 a) internal { 64 | bool result; 65 | 66 | assembly { 67 | // Get a pointer to some free memory. 68 | let pointer := mload(0x40) 69 | 70 | // Write the abi-encoded calldata to memory piece by piece: 71 | mstore(pointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // Begin with the function selector. 72 | mstore(add(pointer, 4), and(f, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "from" argument. 73 | mstore(add(pointer, 36), and(t, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. 74 | mstore(add(pointer, 68), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value. 75 | 76 | // Call the token and store if it succeeded or not. 77 | // We use 100 because the calldata length is 4 + 32 * 3. 78 | result := call(gas(), e, 0, pointer, 100, 0, 0) 79 | } 80 | 81 | require(success(result), "transfer from failed"); 82 | } 83 | 84 | /// @notice normalize the acceptable values of true or null vs the unacceptable value of false (or something malformed) 85 | /// @param r Return value from the assembly `call()` to Erc20['selector'] 86 | function success(bool r) private pure returns (bool) { 87 | bool result; 88 | 89 | assembly { 90 | // Get how many bytes the call returned. 91 | let returnDataSize := returndatasize() 92 | 93 | // If the call reverted: 94 | if iszero(r) { 95 | // Copy the revert message into memory. 96 | returndatacopy(0, 0, returnDataSize) 97 | 98 | // Revert with the same message. 99 | revert(0, returnDataSize) 100 | } 101 | 102 | switch returnDataSize 103 | case 32 { 104 | // Copy the return data into memory. 105 | returndatacopy(0, 0, returnDataSize) 106 | 107 | // Set success to whether it returned true. 108 | result := iszero(iszero(mload(0))) 109 | } 110 | case 0 { 111 | // There was no return data. 112 | result := 1 113 | } 114 | default { 115 | // It returned some malformed input. 116 | result := 0 117 | } 118 | } 119 | 120 | return result; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/Swivel-Finance/gost.svg?token=mHzJQzb11WHSPwztZw8B&branch=main)](https://travis-ci.com/Swivel-Finance/gost) 2 | ##### Smart contract testing with Geth via the Golang ABIGEN 3 | ``` 4 | _______ 5 | /______/\ 6 | \__::::\/ 7 | _______ ______ ______ _________ 8 | /______/\ /_____/\ /_____/\ /________/\ 9 | \::::__\/__ \:::_ \ \ \::::_\/_ \__.::.__\/ ,--. 10 | \:\ /____/\ \:\ \ \ \ \:\/___/\ \::\ \ | oo| 11 | \:\\_ _\/ \:\ \ \ \ \_::._\:\ \::\ \ | ~~| o o o o o o o o o o 12 | \:\_\ \ \ \:\_\ \ \ /____\:\ \::\ \ |/\/\| 13 | \_____\/ \_____\/ \_____\/ \__\/ 14 | ``` 15 | ## Getting Started 16 | This project contains the Swivel Smart Contracts and Libraries which have been compiled to their `abi` and `bin` components, those then transformed into 17 | golang bindings via the Geth `abigen` tool. You can see the commands used to perform these tasks in the `Makefile`. 18 | 19 | Tests are located in `/pkg/*testing/`. For example, the unit tests for the Swivel.sol contract will be located in `/pkg/swiveltesting`. 20 | 21 | Existing tests, and any newly created, are always run via `go test [-v] ./...` from the project root. 22 | 23 | If you are only here to view the existing tests, and maybe pull this repo and run it you can stop here. If you are a developer writing new tests or simply 24 | an interested party who realizes the *mind blowing* superiority of testing your smart contracts this way - read on! 25 | 26 | #### Notes on /build 27 | The `/build` directory exists to house our 2 deployed contracts (and 2 deployable child contracts) with all of their dependencies and compiled artifacts. Those being not only the `.sol` files but the 28 | `.bin`, `.abi` and `.go` files as well. These compiled artifacts will be different from what is in the `/test` directories, as those are compiled with mocks in place (these are not). 29 | 30 | Note that the files here should be considered "alpha" stage as what is present [here](https://github.com/Swivel-Finance/swivel/tree/main/contracts) should be considered "stable". 31 | 32 | ### Geth 33 | Gost only depends on Geth itself. We _do_ list `solc` as being necessary, but that isn't _exactly_ true. 34 | If you are in possesion of the `abi` and `bin` files of any smart contract (regardless of language used) the 35 | Makefile's `compile_*` type steps can be made for them. 36 | 37 | As this Golang project is done with modules, you don't need to specifically `go get` anything. All dependencies will be fetched on your first test run. 38 | 39 | ### Solidity Compiler 40 | First, assure that `solc` is in your $PATH. If not, [make it so](https://docs.soliditylang.org/en/v0.8.0/installing-solidity.html). As stated above, 41 | this is assuming your smart contracts are `.sol` and you do not already have the `.abi` and `.bin` files. Add new Makefile rules for other languages 42 | (Vyper for example) and compilers if you are so inclined. 43 | 44 | ### Geth ABIGEN 45 | Second, you will need `abigen` in your $PATH. You can, for example, follow the instructions [here](https://github.com/ethereum/go-ethereum). 46 | Note that you only need to use the `devtools` rule from the go-ethereum Makefile. i.e. `make devtools` will suffice. 47 | 48 | ### Project Structure 49 | As always we use the [project layout](https://github.com/golang-standards/project-layout) guidelines. This interpretation places the 50 | Solidity files into `test/` with their compiled artifacts going into aptly named subdirectories for ease of use with 51 | golang's package conventions (see Makefile). Some notes on the important residents of `/test/`: 52 | 53 | * swivel/ 54 | * Swivel.sol. The current Swivel smart contract 55 | * Abstracts.sol. The external interfaces declared by Swivel.sol 56 | * Sig.sol. Imbeddable library used by Swivel.sol 57 | * Hash.sol. Imbeddable library used by Swivel.sol 58 | * swivel.go. Autogenerated Geth bindings for use in tests. 59 | * marketplace/ 60 | * MarketPlace.sol The current MarketPlace smart contract 61 | * Abstracts.sol. The external interfaces declared by MarketPlace.sol 62 | * VaultTracker.sol. A mock implementation of the VaultTracker smart contract. Used to build and test MarketPlace in isolation 63 | * ZcToken.sol. A mock implementation of the ZcToken smart contract. 64 | * marketplace.go. Autogenerated Geth bindings. 65 | * vaulttracker/ 66 | * VaultTracker.sol. The current VaultTracker smart contract 67 | * tokens/ 68 | * ZcToken.sol. An Open Zeppelin clone of an extended Erc20 with mint, burn and permit. 69 | 70 | #### The Makefile 71 | This is the way. 72 | 73 | Steps for setting-up, tearnig-down and testing the individual pieces of the repo are here. 74 | 75 | #### Compiling and Testing Your Contracts 76 | * Compiling: 77 | * `make all` 78 | 79 | If you wish to run the steps separately or run into any errors that need debugging, see the Makefile for the entire list of available commands. 80 | 81 | * Testing: 82 | * `go test ./...` from root (as stated). Add the -v flag if you expect to see any logging you may be doing. 83 | * You can of course test at the package level: `go test ./path/to/package` 84 | -------------------------------------------------------------------------------- /pkg/swiveltesting/split_and_combine_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | // "github.com/ethereum/go-ethereum/common" 9 | // "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/suite" 12 | // "github.com/swivel-finance/gost/internal/helpers" 13 | // "github.com/swivel-finance/gost/test/fakes" 14 | "github.com/swivel-finance/gost/test/mocks" 15 | "github.com/swivel-finance/gost/test/swivel" 16 | ) 17 | 18 | type splitCombineSuite struct { 19 | suite.Suite 20 | Env *Env 21 | Dep *Dep 22 | Erc20 *mocks.Erc20Session 23 | CErc20 *mocks.CErc20Session 24 | MarketPlace *mocks.MarketPlaceSession 25 | Swivel *swivel.SwivelSession 26 | } 27 | 28 | func (s *splitCombineSuite) SetupTest() { 29 | var err error 30 | 31 | s.Env = NewEnv(big.NewInt(ONE_ETH)) 32 | s.Dep, err = Deploy(s.Env) 33 | 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | s.Erc20 = &mocks.Erc20Session{ 39 | Contract: s.Dep.Erc20, 40 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 41 | TransactOpts: bind.TransactOpts{ 42 | From: s.Env.Owner.Opts.From, 43 | Signer: s.Env.Owner.Opts.Signer, 44 | }, 45 | } 46 | 47 | s.CErc20 = &mocks.CErc20Session{ 48 | Contract: s.Dep.CErc20, 49 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 50 | TransactOpts: bind.TransactOpts{ 51 | From: s.Env.Owner.Opts.From, 52 | Signer: s.Env.Owner.Opts.Signer, 53 | }, 54 | } 55 | 56 | s.MarketPlace = &mocks.MarketPlaceSession{ 57 | Contract: s.Dep.MarketPlace, 58 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 59 | TransactOpts: bind.TransactOpts{ 60 | From: s.Env.Owner.Opts.From, 61 | Signer: s.Env.Owner.Opts.Signer, 62 | }, 63 | } 64 | 65 | // binding owner to both, kind of why it exists - but could be any of the env wallets 66 | s.Swivel = &swivel.SwivelSession{ 67 | Contract: s.Dep.Swivel, 68 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 69 | TransactOpts: bind.TransactOpts{ 70 | From: s.Env.Owner.Opts.From, 71 | Signer: s.Env.Owner.Opts.Signer, 72 | }, 73 | } 74 | } 75 | 76 | func (s *splitCombineSuite) TestSplit() { 77 | assert := assert.New(s.T()) 78 | 79 | // stub the underlying to return true or the Safe lib will revert 80 | tx, err := s.Erc20.TransferFromReturns(true) 81 | assert.NotNil(tx) 82 | assert.Nil(err) 83 | 84 | // stub (Cerc20) to return 0 85 | tx, err = s.CErc20.MintReturns(big.NewInt(0)) 86 | assert.NotNil(tx) 87 | assert.Nil(err) 88 | s.Env.Blockchain.Commit() 89 | 90 | // the marketplace mock... 91 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 92 | assert.NotNil(tx) 93 | assert.Nil(err) 94 | s.Env.Blockchain.Commit() 95 | 96 | tx, err = s.MarketPlace.MintZcTokenAddingNotionalReturns(true) 97 | assert.NotNil(tx) 98 | assert.Nil(err) 99 | s.Env.Blockchain.Commit() 100 | 101 | maturity := big.NewInt(123456789) 102 | amount := big.NewInt(1000) 103 | 104 | tx, err = s.Swivel.SplitUnderlying(s.Dep.Erc20Address, maturity, amount) 105 | 106 | assert.Nil(err) 107 | assert.NotNil(tx) 108 | s.Env.Blockchain.Commit() 109 | 110 | // we just care that the args were passed thru... 111 | splitArgs, err := s.MarketPlace.MintZcTokenAddingNotionalCalled(s.Dep.Erc20Address) 112 | assert.Nil(err) 113 | assert.NotNil(splitArgs) 114 | assert.Equal(splitArgs.Maturity, maturity) 115 | assert.Equal(splitArgs.Amount, amount) 116 | // msg.sender will be owner here 117 | assert.Equal(splitArgs.One, s.Env.Owner.Opts.From) 118 | } 119 | 120 | func (s *splitCombineSuite) TestCombine() { 121 | assert := assert.New(s.T()) 122 | 123 | // stub the underlying to return true or the Safe lib will revert 124 | tx, err := s.Erc20.TransferReturns(true) 125 | assert.NotNil(tx) 126 | assert.Nil(err) 127 | 128 | // stub (Cerc20) to return 0 129 | tx, err = s.CErc20.RedeemUnderlyingReturns(big.NewInt(0)) 130 | assert.NotNil(tx) 131 | assert.Nil(err) 132 | s.Env.Blockchain.Commit() 133 | 134 | // the marketplace mock... 135 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 136 | assert.NotNil(tx) 137 | assert.Nil(err) 138 | s.Env.Blockchain.Commit() 139 | 140 | tx, err = s.MarketPlace.BurnZcTokenRemovingNotionalReturns(true) 141 | assert.NotNil(tx) 142 | assert.Nil(err) 143 | s.Env.Blockchain.Commit() 144 | 145 | maturity := big.NewInt(123456789) 146 | amount := big.NewInt(1000) 147 | 148 | tx, err = s.Swivel.CombineTokens(s.Dep.Erc20Address, maturity, amount) 149 | 150 | assert.Nil(err) 151 | assert.NotNil(tx) 152 | s.Env.Blockchain.Commit() 153 | 154 | // we just care that the args were passed thru... 155 | combineArgs, err := s.MarketPlace.BurnZcTokenRemovingNotionalCalled(s.Dep.Erc20Address) 156 | assert.Nil(err) 157 | assert.NotNil(combineArgs) 158 | assert.Equal(combineArgs.Maturity, maturity) 159 | assert.Equal(combineArgs.Amount, amount) 160 | // msg.sender will be owner here 161 | assert.Equal(combineArgs.One, s.Env.Owner.Opts.From) 162 | } 163 | 164 | func TestSplitCombineSuite(t *test.T) { 165 | suite.Run(t, &splitCombineSuite{}) 166 | } 167 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/p2p_zc_token_exchange_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/marketplace" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | ) 13 | 14 | type p2pZCTokenExchangeSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 21 | } 22 | 23 | func (s *p2pZCTokenExchangeSuite) SetupTest() { 24 | var err error 25 | 26 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 27 | s.Dep, err = Deploy(s.Env) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | s.Env.Blockchain.Commit() 39 | 40 | s.Erc20 = &mocks.Erc20Session{ 41 | Contract: s.Dep.Erc20, 42 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 43 | TransactOpts: bind.TransactOpts{ 44 | From: s.Env.Owner.Opts.From, 45 | Signer: s.Env.Owner.Opts.Signer, 46 | }, 47 | } 48 | 49 | s.CErc20 = &mocks.CErc20Session{ 50 | Contract: s.Dep.CErc20, 51 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 52 | TransactOpts: bind.TransactOpts{ 53 | From: s.Env.Owner.Opts.From, 54 | Signer: s.Env.Owner.Opts.Signer, 55 | }, 56 | } 57 | 58 | // binding owner to both, kind of why it exists - but could be any of the env wallets 59 | s.MarketPlace = &marketplace.MarketPlaceSession{ 60 | Contract: s.Dep.MarketPlace, 61 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 62 | TransactOpts: bind.TransactOpts{ 63 | From: s.Env.Owner.Opts.From, 64 | Signer: s.Env.Owner.Opts.Signer, 65 | }, 66 | } 67 | 68 | s.MarketPlace.SetSwivelAddress(s.Env.Owner.Opts.From) 69 | s.Env.Blockchain.Commit() 70 | } 71 | 72 | func (s *p2pZCTokenExchangeSuite) TestExchangeFailsWhenPaused() { 73 | assert := assertions.New(s.T()) 74 | 75 | underlying := s.Dep.Erc20Address 76 | maturity := big.NewInt(123456789) 77 | 78 | tx, err := s.MarketPlace.Pause(true) 79 | assert.Nil(err) 80 | assert.NotNil(tx) 81 | s.Env.Blockchain.Commit() 82 | 83 | amount := big.NewInt(100) 84 | tx, err = s.MarketPlace.P2pZcTokenExchange(underlying, maturity, s.Env.Owner.Opts.From, s.Env.User1.Opts.From, amount) 85 | 86 | assert.Nil(tx) 87 | assert.NotNil(err) 88 | assert.Regexp("markets are paused", err.Error()) 89 | 90 | // unpause so the other tests don't fail 91 | tx, err = s.MarketPlace.Pause(false) 92 | assert.Nil(err) 93 | assert.NotNil(tx) 94 | s.Env.Blockchain.Commit() 95 | } 96 | 97 | func (s *p2pZCTokenExchangeSuite) TestP2PZCTokenExchange() { 98 | assert := assertions.New(s.T()) 99 | underlying := s.Dep.Erc20Address 100 | maturity := s.Dep.Maturity 101 | ctoken := s.Dep.CErc20Address 102 | 103 | tx, err := s.Erc20.DecimalsReturns(uint8(18)) 104 | assert.Nil(err) 105 | assert.NotNil(tx) 106 | s.Env.Blockchain.Commit() 107 | 108 | tx, err = s.CErc20.UnderlyingReturns(underlying) 109 | assert.Nil(err) 110 | assert.NotNil(tx) 111 | s.Env.Blockchain.Commit() 112 | 113 | tx, err = s.MarketPlace.CreateMarket( 114 | maturity, 115 | ctoken, 116 | "awesome market", 117 | "AM", 118 | ) 119 | 120 | assert.Nil(err) 121 | assert.NotNil(tx) 122 | 123 | s.Env.Blockchain.Commit() 124 | 125 | ownerOpts := s.Env.Owner.Opts 126 | user1Opts := s.Env.User1.Opts 127 | 128 | // we should be able to fetch the market now... 129 | market, err := s.MarketPlace.Markets(underlying, maturity) 130 | assert.Nil(err) 131 | assert.Equal(market.CTokenAddr, ctoken) 132 | 133 | s.Env.Blockchain.Commit() 134 | 135 | zcTokenContract, err := mocks.NewZcToken(market.ZcTokenAddr, s.Env.Blockchain) 136 | zcToken := &mocks.ZcTokenSession{ 137 | Contract: zcTokenContract, 138 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 139 | TransactOpts: bind.TransactOpts{ 140 | From: s.Env.Owner.Opts.From, 141 | Signer: s.Env.Owner.Opts.Signer, 142 | }, 143 | } 144 | 145 | zcMaturity, err := zcToken.Maturity() 146 | assert.Equal(maturity, zcMaturity) 147 | 148 | s.Env.Blockchain.Commit() 149 | 150 | tx, err = zcToken.BurnReturns(true) 151 | assert.Nil(err) 152 | assert.NotNil(tx) 153 | 154 | s.Env.Blockchain.Commit() 155 | 156 | tx, err = zcToken.MintReturns(true) 157 | assert.Nil(err) 158 | assert.NotNil(tx) 159 | 160 | s.Env.Blockchain.Commit() 161 | 162 | amount := big.NewInt(100) 163 | tx, err = s.MarketPlace.P2pZcTokenExchange(underlying, maturity, ownerOpts.From, user1Opts.From, amount) 164 | assert.Nil(err) 165 | assert.NotNil(tx) 166 | 167 | s.Env.Blockchain.Commit() 168 | 169 | burnAmt, err := zcToken.BurnCalled(ownerOpts.From) 170 | assert.Nil(err) 171 | assert.Equal(amount, burnAmt) 172 | 173 | mintAmt, err := zcToken.MintCalled(user1Opts.From) 174 | assert.Nil(err) 175 | assert.Equal(amount, mintAmt) 176 | } 177 | 178 | func TestP2PZCTokenExchangeSuite(t *test.T) { 179 | suite.Run(t, &p2pZCTokenExchangeSuite{}) 180 | } 181 | -------------------------------------------------------------------------------- /pkg/marketplacetesting/redeem_vault_interest_test.go: -------------------------------------------------------------------------------- 1 | package marketplacetesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/marketplace" 11 | "github.com/swivel-finance/gost/test/mocks" 12 | ) 13 | 14 | type redeemVaultInterestSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *marketplace.MarketPlaceSession // *Session objects are created by the go bindings 21 | } 22 | 23 | func (s *redeemVaultInterestSuite) SetupTest() { 24 | var err error 25 | assert := assertions.New(s.T()) 26 | 27 | s.Env = NewEnv(big.NewInt(ONE_ETH)) // each of the wallets in the env will begin with this balance 28 | s.Dep, err = Deploy(s.Env) 29 | assert.Nil(err) 30 | 31 | err = s.Env.Blockchain.AdjustTime(0) // set bc timestamp to 0 32 | assert.Nil(err) 33 | s.Env.Blockchain.Commit() 34 | 35 | s.Erc20 = &mocks.Erc20Session{ 36 | Contract: s.Dep.Erc20, 37 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 38 | TransactOpts: bind.TransactOpts{ 39 | From: s.Env.Owner.Opts.From, 40 | Signer: s.Env.Owner.Opts.Signer, 41 | }, 42 | } 43 | 44 | s.CErc20 = &mocks.CErc20Session{ 45 | Contract: s.Dep.CErc20, 46 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 47 | TransactOpts: bind.TransactOpts{ 48 | From: s.Env.Owner.Opts.From, 49 | Signer: s.Env.Owner.Opts.Signer, 50 | }, 51 | } 52 | 53 | s.MarketPlace = &marketplace.MarketPlaceSession{ 54 | Contract: s.Dep.MarketPlace, 55 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 56 | TransactOpts: bind.TransactOpts{ 57 | From: s.Env.Owner.Opts.From, 58 | Signer: s.Env.Owner.Opts.Signer, 59 | }, 60 | } 61 | 62 | // the swivel address must be set (set to owner accomodating the onlySwivel calls) 63 | _, err = s.MarketPlace.SetSwivelAddress(s.Env.Owner.Opts.From) 64 | assert.Nil(err) 65 | s.Env.Blockchain.Commit() 66 | } 67 | 68 | func (s *redeemVaultInterestSuite) TestRedeemFailsWhenPaused() { 69 | assert := assertions.New(s.T()) 70 | 71 | tx, err := s.MarketPlace.Pause(true) 72 | assert.Nil(err) 73 | assert.NotNil(tx) 74 | s.Env.Blockchain.Commit() 75 | 76 | maturity := big.NewInt(123456789) 77 | 78 | tx, err = s.MarketPlace.RedeemVaultInterest(s.Dep.Erc20Address, maturity, s.Env.Owner.Opts.From) 79 | 80 | assert.Nil(tx) 81 | assert.NotNil(err) 82 | assert.Regexp("markets are paused", err.Error()) 83 | 84 | // unpause so the other tests don't fail 85 | tx, err = s.MarketPlace.Pause(false) 86 | assert.Nil(err) 87 | assert.NotNil(tx) 88 | s.Env.Blockchain.Commit() 89 | } 90 | 91 | func (s *redeemVaultInterestSuite) TestRedeemVaultInterest() { 92 | assert := assertions.New(s.T()) 93 | maturity := s.Dep.Maturity 94 | ctokenAddr := s.Dep.CErc20Address 95 | 96 | tx, err := s.Erc20.DecimalsReturns(uint8(18)) 97 | assert.Nil(err) 98 | assert.NotNil(tx) 99 | s.Env.Blockchain.Commit() 100 | 101 | tx, err = s.CErc20.UnderlyingReturns(s.Dep.Erc20Address) 102 | assert.Nil(err) 103 | assert.NotNil(tx) 104 | s.Env.Blockchain.Commit() 105 | 106 | tx, err = s.MarketPlace.CreateMarket( 107 | maturity, 108 | ctokenAddr, 109 | "awesome market", 110 | "AM", 111 | ) 112 | 113 | assert.Nil(err) 114 | assert.NotNil(tx) 115 | s.Env.Blockchain.Commit() 116 | 117 | // we should be able to fetch the market now... 118 | market, err := s.MarketPlace.Markets(s.Dep.Erc20Address, maturity) 119 | assert.Nil(err) 120 | assert.Equal(market.CTokenAddr, ctokenAddr) 121 | 122 | zcTokenContract, err := mocks.NewZcToken(market.ZcTokenAddr, s.Env.Blockchain) 123 | zcToken := &mocks.ZcTokenSession{ 124 | Contract: zcTokenContract, 125 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 126 | TransactOpts: bind.TransactOpts{ 127 | From: s.Env.Owner.Opts.From, 128 | Signer: s.Env.Owner.Opts.Signer, 129 | }, 130 | } 131 | 132 | zcMaturity, err := zcToken.Maturity() 133 | assert.Equal(maturity, zcMaturity) 134 | 135 | vaultTrackerContract, err := mocks.NewVaultTracker(market.VaultAddr, s.Env.Blockchain) 136 | vaultTracker := &mocks.VaultTrackerSession{ 137 | Contract: vaultTrackerContract, 138 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 139 | TransactOpts: bind.TransactOpts{ 140 | From: s.Env.Owner.Opts.From, 141 | Signer: s.Env.Owner.Opts.Signer, 142 | }, 143 | } 144 | 145 | vaultInterest := big.NewInt(123456789) 146 | tx, err = vaultTracker.RedeemInterestReturns(vaultInterest) 147 | assert.Nil(err) 148 | assert.NotNil(tx) 149 | 150 | s.Env.Blockchain.Commit() 151 | 152 | tx, err = s.MarketPlace.RedeemVaultInterest(s.Dep.Erc20Address, maturity, s.Env.Owner.Opts.From) 153 | assert.Nil(err) 154 | assert.NotNil(tx) 155 | 156 | s.Env.Blockchain.Commit() 157 | 158 | // the vaulttracker mock should now retain the address it was passed 159 | address, err := vaultTracker.RedeemInterestCalled() 160 | assert.Nil(err) 161 | assert.NotNil(address) 162 | assert.Equal(address, s.Env.Owner.Opts.From) 163 | } 164 | 165 | func TestRedeemVaultInterestSuite(t *test.T) { 166 | suite.Run(t, &redeemVaultInterestSuite{}) 167 | } 168 | -------------------------------------------------------------------------------- /pkg/swiveltesting/redeem_vault_interest_test.go: -------------------------------------------------------------------------------- 1 | package swiveltesting 2 | 3 | import ( 4 | "math/big" 5 | test "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 | assertions "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | "github.com/swivel-finance/gost/test/mocks" 11 | "github.com/swivel-finance/gost/test/swivel" 12 | ) 13 | 14 | type redeemVaultInterestSuite struct { 15 | suite.Suite 16 | Env *Env 17 | Dep *Dep 18 | Erc20 *mocks.Erc20Session 19 | CErc20 *mocks.CErc20Session 20 | MarketPlace *mocks.MarketPlaceSession 21 | Swivel *swivel.SwivelSession 22 | } 23 | 24 | func (s *redeemVaultInterestSuite) SetupTest() { 25 | var err error 26 | 27 | s.Env = NewEnv(big.NewInt(ONE_ETH)) 28 | s.Dep, err = Deploy(s.Env) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | s.Erc20 = &mocks.Erc20Session{ 35 | Contract: s.Dep.Erc20, 36 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 37 | TransactOpts: bind.TransactOpts{ 38 | From: s.Env.Owner.Opts.From, 39 | Signer: s.Env.Owner.Opts.Signer, 40 | }, 41 | } 42 | 43 | s.CErc20 = &mocks.CErc20Session{ 44 | Contract: s.Dep.CErc20, 45 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 46 | TransactOpts: bind.TransactOpts{ 47 | From: s.Env.Owner.Opts.From, 48 | Signer: s.Env.Owner.Opts.Signer, 49 | }, 50 | } 51 | 52 | s.MarketPlace = &mocks.MarketPlaceSession{ 53 | Contract: s.Dep.MarketPlace, 54 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 55 | TransactOpts: bind.TransactOpts{ 56 | From: s.Env.Owner.Opts.From, 57 | Signer: s.Env.Owner.Opts.Signer, 58 | }, 59 | } 60 | 61 | s.Swivel = &swivel.SwivelSession{ 62 | Contract: s.Dep.Swivel, 63 | CallOpts: bind.CallOpts{From: s.Env.Owner.Opts.From, Pending: false}, 64 | TransactOpts: bind.TransactOpts{ 65 | From: s.Env.Owner.Opts.From, 66 | Signer: s.Env.Owner.Opts.Signer, 67 | }, 68 | } 69 | } 70 | 71 | func (s *redeemZcTokenSuite) TestRedeemVaultInterest() { 72 | assert := assertions.New(s.T()) 73 | underlying := s.Dep.Erc20Address 74 | maturity := s.Dep.Maturity 75 | 76 | // stub the underlying to return true or the Safe lib will revert 77 | tx, err := s.Erc20.TransferReturns(true) 78 | assert.NotNil(tx) 79 | assert.Nil(err) 80 | 81 | tx, err = s.CErc20.RedeemUnderlyingReturns(big.NewInt(0)) 82 | assert.NotNil(tx) 83 | assert.Nil(err) 84 | 85 | s.Env.Blockchain.Commit() 86 | 87 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 88 | assert.Nil(err) 89 | assert.NotNil(tx) 90 | 91 | s.Env.Blockchain.Commit() 92 | 93 | redeemed := big.NewInt(12345) 94 | tx, err = s.MarketPlace.RedeemVaultInterestReturns(redeemed) 95 | assert.Nil(err) 96 | assert.NotNil(tx) 97 | 98 | s.Env.Blockchain.Commit() 99 | 100 | tx, err = s.Swivel.RedeemVaultInterest(underlying, maturity) 101 | assert.Nil(err) 102 | assert.NotNil(tx) 103 | 104 | s.Env.Blockchain.Commit() 105 | 106 | // the marketplace mock should have been called with the msg.sender address 107 | redeemArgs, err := s.MarketPlace.RedeemVaultInterestCalled(underlying) 108 | assert.Nil(err) 109 | assert.NotNil(redeemArgs) 110 | assert.Equal(redeemArgs.One, s.Env.Owner.Opts.From) 111 | 112 | // underlying tranfer should have been called with an amount redeemed 113 | transferred, err := s.Erc20.TransferCalled(s.Env.Owner.Opts.From) 114 | assert.Nil(err) 115 | assert.Equal(redeemed, transferred) 116 | } 117 | 118 | func (s *redeemZcTokenSuite) TestRedeemSwivelVaultInterest() { 119 | assert := assertions.New(s.T()) 120 | underlying := s.Dep.Erc20Address 121 | maturity := s.Dep.Maturity 122 | 123 | // stub the underlying to return true or the Safe lib will revert 124 | tx, err := s.Erc20.TransferReturns(true) 125 | assert.NotNil(tx) 126 | assert.Nil(err) 127 | 128 | tx, err = s.CErc20.RedeemUnderlyingReturns(big.NewInt(0)) 129 | assert.NotNil(tx) 130 | assert.Nil(err) 131 | 132 | s.Env.Blockchain.Commit() 133 | 134 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 135 | assert.Nil(err) 136 | assert.NotNil(tx) 137 | 138 | s.Env.Blockchain.Commit() 139 | 140 | redeemed := big.NewInt(567890) 141 | tx, err = s.MarketPlace.RedeemVaultInterestReturns(redeemed) 142 | assert.Nil(err) 143 | assert.NotNil(tx) 144 | 145 | s.Env.Blockchain.Commit() 146 | 147 | tx, err = s.Swivel.RedeemSwivelVaultInterest(underlying, maturity) 148 | assert.Nil(err) 149 | assert.NotNil(tx) 150 | 151 | s.Env.Blockchain.Commit() 152 | 153 | // the marketplace mock should have been called with swivel address 154 | redeemArgs, err := s.MarketPlace.RedeemVaultInterestCalled(underlying) 155 | assert.Nil(err) 156 | assert.NotNil(redeemArgs) 157 | assert.Equal(redeemArgs.One, s.Dep.SwivelAddress) 158 | 159 | } 160 | 161 | func (s *redeemZcTokenSuite) TestRedeemVaultInterestUnderlyingFails() { 162 | assert := assertions.New(s.T()) 163 | underlying := s.Dep.Erc20Address 164 | maturity := s.Dep.Maturity 165 | 166 | tx, err := s.CErc20.RedeemUnderlyingReturns(big.NewInt(1)) 167 | assert.NotNil(tx) 168 | assert.Nil(err) 169 | 170 | s.Env.Blockchain.Commit() 171 | 172 | tx, err = s.MarketPlace.CTokenAddressReturns(s.Dep.CErc20Address) // must use the actual dep addr here 173 | assert.Nil(err) 174 | assert.NotNil(tx) 175 | s.Env.Blockchain.Commit() 176 | 177 | tx, err = s.MarketPlace.RedeemVaultInterestReturns(big.NewInt(12345)) 178 | assert.Nil(err) 179 | assert.NotNil(tx) 180 | 181 | s.Env.Blockchain.Commit() 182 | 183 | tx, err = s.Swivel.RedeemVaultInterest(underlying, maturity) 184 | assert.NotNil(err) 185 | assert.Regexp("compound redemption failed", err.Error()) 186 | assert.Nil(tx) 187 | } 188 | 189 | func TestRedeemVaultInterestSuite(t *test.T) { 190 | suite.Run(t, &redeemVaultInterestSuite{}) 191 | } 192 | --------------------------------------------------------------------------------