1
+ import { getBalance , sendCoins } from 'bank' ;
2
+
3
+ const store = ( storeDefinition ) => {
4
+ return ( target , state ) => {
5
+ Object . entries ( storeDefinition ) . forEach ( ( [ key , value ] ) => {
6
+ if ( typeof value === 'function' ) {
7
+ // For functions, create a getter and a setter
8
+ Object . defineProperty ( target , key , {
9
+ get ( ) {
10
+ return ( ...args ) => state . get ( [ key , ...args ] . join ( '/' ) ) ?? value ( undefined ) ; // TODO: support nested mappings
11
+ } ,
12
+ enumerable : true ,
13
+ } ) ;
14
+
15
+ const setterName = `set${ key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) } ` ;
16
+ Object . defineProperty ( target , setterName , {
17
+ value : ( ...args ) => {
18
+ const newValue = args [ args . length - 1 ] ;
19
+ const keys = args . slice ( 0 , - 1 ) ;
20
+ state . set ( [ key , ...keys ] . join ( '/' ) , newValue ) ;
21
+ } ,
22
+ enumerable : false ,
23
+ } ) ;
24
+ } else {
25
+ // For non-functions, create a getter and a setter
26
+ Object . defineProperty ( target , key , {
27
+ get ( ) {
28
+ return state . get ( key ) ?? value ;
29
+ } ,
30
+ set ( newValue ) {
31
+ state . set ( key , newValue ) ;
32
+ } ,
33
+ enumerable : true ,
34
+ } ) ;
35
+
36
+ const setterName = `set${ key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) } ` ;
37
+ Object . defineProperty ( target , setterName , {
38
+ value : ( newValue ) => {
39
+ state . set ( key , newValue ) ;
40
+ } ,
41
+ enumerable : false ,
42
+ } ) ;
43
+ }
44
+ } ) ;
45
+ } ;
46
+ } ;
47
+
48
+ //////////////////////////////////////////////////////////////////////////////////
49
+
50
+ const useStore = store ( {
51
+ totalSupply : 0 ,
52
+ balance :address => 0 ,
53
+ reserves : [ 0 , 0 ] ,
54
+ } ) ;
55
+ // should construct
56
+ // this.totalSupply // getter
57
+ // this.setTotalSupply(value) // setter
58
+ // this.balance(address) // getter
59
+ // this.setBalance(address, value) // setter
60
+ // this.reserves // getter
61
+ // this.setReserves(value) // setter
62
+ export default class AMMContract {
63
+ constructor ( state , { msg, address} ) {
64
+ this . msg = msg ;
65
+ this . address = address ;
66
+
67
+ useStore ( this , state ) ;
68
+ }
69
+
70
+ token0 = 'USDC' ;
71
+ token1 = 'ATOM' ;
72
+
73
+ getTotalSupply ( ) {
74
+ return this . totalSupply ;
75
+ }
76
+
77
+ getBalance ( address ) {
78
+ return this . balance ( address ) ;
79
+ }
80
+
81
+ getReserves ( ) {
82
+ return this . reserves ;
83
+ }
84
+
85
+ #getBankBalance( address , token ) {
86
+ return getBalance ( address , token ) ;
87
+ }
88
+
89
+ #mint( to , amount ) {
90
+ const balance = this . balance ( to ) ;
91
+ this . setBalance ( to , balance + amount ) ;
92
+ this . totalSupply += amount ;
93
+ }
94
+
95
+ #burn( from , amount ) {
96
+ const balance = this . balance ( from ) ;
97
+ if ( balance < amount ) {
98
+ throw Error ( 'insufficient balance' ) ;
99
+ }
100
+ this . setBalance ( from , balance - amount ) ;
101
+ this . totalSupply -= amount ;
102
+ }
103
+
104
+ #update( amount0 , amount1 ) {
105
+ const [ reserve0 , reserve1 ] = this . reserves ;
106
+ this . reserves = [
107
+ reserve0 + amount0 ,
108
+ reserve1 + amount1 ,
109
+ ] ;
110
+ }
111
+
112
+ swap ( { tokenIn, amountIn} ) {
113
+ const isToken0 = tokenIn == this . token0 ;
114
+ const isToken1 = tokenIn == this . token1 ;
115
+
116
+ if ( ! isToken0 && ! isToken1 ) {
117
+ throw Error ( 'invalid token' ) ;
118
+ }
119
+
120
+ const [ reserve0 , reserve1 ] = this . reserves ;
121
+ let tokenOut , reserveIn , reserveOut ;
122
+
123
+ [ tokenIn , tokenOut , reserveIn , reserveOut ] =
124
+ isToken0
125
+ ? [ this . token0 , this . token1 , reserve0 , reserve1 ]
126
+ : [ this . token1 , this . token0 , reserve1 , reserve0 ] ;
127
+
128
+ sendCoins ( this . msg . sender , this . address , {
129
+ [ tokenIn ] : amountIn ,
130
+ } ) ;
131
+
132
+ const amountInWithFee = amountIn * 997 / 1000 ;
133
+ const amountOut = ( reserveOut * amountInWithFee ) / ( reserveIn + amountInWithFee ) ;
134
+
135
+ sendCoins ( this . address , this . msg . sender , {
136
+ [ tokenOut ] : amountOut ,
137
+ } ) ;
138
+
139
+ this . #update(
140
+ this . #getBankBalance( this . address , this . token0 ) . amount ,
141
+ this . #getBankBalance( this . address , this . token1 ) . amount ,
142
+ ) ;
143
+
144
+ return amountOut ;
145
+ }
146
+
147
+ addLiquidity ( { amount0, amount1} ) {
148
+ sendCoins ( this . msg . sender , this . address , {
149
+ [ this . token0 ] : amount0 ,
150
+ [ this . token1 ] : amount1 ,
151
+ } ) ;
152
+
153
+ const [ reserve0 , reserve1 ] = this . reserves ;
154
+
155
+ if ( reserve0 > 0 || reserve1 > 0 ) {
156
+ if ( reserve0 * amount1 != reserve1 * amount0 ) {
157
+ throw Error ( 'invalid liquidity' ) ;
158
+ }
159
+ }
160
+
161
+ let shares = 0 ;
162
+ if ( this . totalSupply > 0 ) {
163
+ shares = Math . sqrt ( amount0 * amount1 ) ;
164
+ } else {
165
+ shares = Math . min (
166
+ ( amount0 * this . totalSupply ) / reserve0 ,
167
+ ( amount1 * this . totalSupply ) / reserve1 ,
168
+ ) ;
169
+ }
170
+
171
+ this . #mint( this . msg . sender , shares ) ;
172
+
173
+ this . #update(
174
+ this . #getBankBalance( this . address , this . token0 ) . amount ,
175
+ this . #getBankBalance( this . address , this . token1 ) . amount ,
176
+ ) ;
177
+
178
+ return shares ;
179
+ }
180
+
181
+ removeLiquidity ( { shares} ) {
182
+ const bal0 = this . #getBankBalance( this . address , this . token0 ) ;
183
+ const bal1 = this . #getBankBalance( this . address , this . token1 ) ;
184
+ const totalSupply = this . totalSupply ;
185
+
186
+ const amount0 = bal0 * shares / totalSupply ;
187
+ const amount1 = bal1 * shares / totalSupply ;
188
+ this . #burn( this . msg . sender , shares ) ;
189
+ this . #update( bal0 - amount0 , bal1 - amount1 ) ;
190
+ sendCoins ( this . address , this . msg . sender , {
191
+ [ this . token0 ] : amount0 ,
192
+ [ this . token1 ] : amount1 ,
193
+ } ) ;
194
+
195
+ return [ amount0 , amount1 ] ;
196
+ }
197
+ }
0 commit comments